¿Por qué usar doble indirección? o ¿Por qué usar punteros a punteros?

272

¿Cuándo se debe usar una doble indirección en C? ¿Alguien puede explicar con un ejemplo?

Lo que sé es que una doble indirección es un puntero a un puntero. ¿Por qué necesitaría un puntero a un puntero?

manju
fuente
49
Ten cuidado; la frase "puntero doble" también se refiere al tipo double*.
Keith Thompson el
1
Tome nota: la respuesta a esta pregunta es diferente para C y C ++: no agregue la etiqueta C + a esta pregunta tan antigua.
B 1овић

Respuestas:

479

Si desea tener una lista de caracteres (una palabra), puede usar char *word

Si desea una lista de palabras (una oración), puede usar char **sentence

Si desea una lista de oraciones (un monólogo), puede usar char ***monologue

Si desea una lista de monólogos (una biografía), puede usar char ****biography

Si desea una lista de biografías (una biobiblioteca), puede usar char *****biolibrary

Si desea una lista de biobibliotecas (a ?? lol), puede usar char ******lol

... ...

Sí, sé que estas podrían no ser las mejores estructuras de datos.


Ejemplo de uso con un lol muy muy muy aburrido

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

Salida:

palabras totales en mi jajaja: 243
pmg
fuente
77
Solo quería señalar que a arr[a][b][c]no es a ***arr. El puntero de punteros usa referencias de referencias, mientras que arr[a][b][c]se almacena como una matriz habitual en el orden principal de la fila.
MCCCS
170

Una razón es que desea cambiar el valor del puntero pasado a una función como argumento de la función, para hacer esto necesita un puntero a un puntero.

En palabras simples, utilícelo **cuando desee conservar (O retener el cambio) la Asignación o asignación de memoria incluso fuera de una llamada de función.(Entonces, pase dicha función con doble puntero arg.)

Puede que este no sea un muy buen ejemplo, pero le mostrará el uso básico:

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}
Asha
fuente
14
¿Qué sería diferente si asignar fuera void allocate(int *p)y lo llamaras como allocate(p)?
ア レ ッ ク ス
@AlexanderSupertramp Sí. El código se segfault. Por favor, vea la respuesta de Silviu.
Abhishek
@Asha, ¿cuál es la diferencia entre asignar (p) y asignar (& p)?
user2979872
1
@Asha - ¿No podemos devolver el puntero? Si debemos mantenerlo vacío, ¿cuál es un caso de uso práctico de este escenario?
Shabirmean
91
  • Digamos que tienes un puntero. Su valor es una dirección.
  • pero ahora quieres cambiar esa dirección.
  • tú podrías. al hacerlo pointer1 = pointer2, le da a pointer1 la dirección de pointer2.
  • ¡pero! si hace eso dentro de una función y desea que el resultado persista después de que se realiza la función, debe realizar un trabajo adicional. necesita un nuevo puntero3 solo para apuntar al puntero1. Pase el puntero3 a la función.

  • Aquí hay un ejemplo. Mire primero la salida a continuación para comprender.

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

Aquí está la salida: ( lea esto primero )

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 
Brian Joseph Spinos
fuente
44
Esta es una gran respuesta y realmente me ayudó a visualizar el propósito y la utilidad de un puntero doble.
Justin
1
@Justin, ¿revisaste mi respuesta arriba de esta? es más limpio :)
Brian Joseph Spinos
10
Gran respuesta, solo falta explicar que <code> void cant_change (int * x, int * z) </code> falla porque sus parámetros son solo nuevos punteros de ámbito local que se inicializan de la misma manera a y f punteros (por lo que no son lo mismo que a y f).
Pedro Reis
1
¿Sencillo? De Verdad? ;)
alk
1
Esta respuesta realmente explica uno de los usos más comunes de puntero a punteros, ¡gracias!
tonyjosi
50

Además de la respuesta de Asha , si usa un puntero único al ejemplo siguiente (por ejemplo, alloc1 ()) perderá la referencia a la memoria asignada dentro de la función.

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

La razón por la que ocurre así es que en alloc1el puntero se pasa por valor. Por lo tanto, cuando se reasigna al resultado de la mallocllamada dentro de alloc1, el cambio no pertenece al código en un ámbito diferente.

Silviu
fuente
1
¿Qué sucede si p es un puntero entero estático? Obteniendo falla de segmentación.
kapilddit
free(p)no es suficiente, es necesario if(p) free(*p)además
Shijing Lv
@ShijingLv: No. se *pevalúa para intmantener el valor de 10, pasar esto inta free () `es una mala idea.
alk
La asignación realizada en alloc1()introduce una pérdida de memoria. El valor del puntero que se pasa libre se pierde al volver de la función.
alk
1
No (!) Es necesario emitir el resultado de malloc en C.
alk
23

Hoy vi un muy buen ejemplo de esta publicación de blog. , como resumiré a continuación.

Imagine que tiene una estructura para nodos en una lista vinculada, que probablemente sea

typedef struct node
{
    struct node * next;
    ....
} node;

Ahora desea implementar una remove_iffunción, que acepte un criterio de eliminación rmcomo uno de los argumentos y atraviese la lista vinculada: si una entrada cumple el criterio (algo así rm(entry)==true), su nodo se eliminará de la lista. Al final, remove_ifdevuelve el encabezado (que puede ser diferente del encabezado original) de la lista vinculada.

Puedes escribir

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

como tu forbucle El mensaje es que, sin punteros dobles, debe mantener una prevvariable para reorganizar los punteros y manejar los dos casos diferentes.

Pero con los punteros dobles, puedes escribir

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

No necesita un prevahora porque puede modificar directamente lo que prev->nextseñaló .

Para aclarar las cosas, sigamos un poco el código. Durante el retiro:

  1. if entry == *head: será *head (==*curr) = *head->next- headahora apunta al puntero del nuevo nodo de encabezado. Para ello, cambia directamente headel contenido de un nuevo puntero.
  2. if entry != *head: de manera similar, *curres lo que prev->nextseñaló y ahora señala entry->next.

No importa en qué caso, puede reorganizar los punteros de manera unificada con punteros dobles.

ziyuang
fuente
22

1. Concepto básico -

Cuando declaras lo siguiente: -

1. char * ch - (llamado puntero de caracteres)
- ch contiene la dirección de un solo carácter.
- (* ch) desreferenciará al valor del personaje.

2. char ** ch -
'ch' contiene la dirección de una matriz de punteros de caracteres. (como en 1)
'* ch' contiene la dirección de un solo carácter. (Tenga en cuenta que es diferente de 1, debido a la diferencia en la declaración).
(** ch) desreferenciará al valor exacto del personaje.

Agregar más punteros expande la dimensión de un tipo de datos, de carácter a cadena, a matriz de cadenas, y así sucesivamente ... Puede relacionarlo con una matriz 1d, 2d, 3d.

Entonces, el uso del puntero depende de cómo lo declare.

Aquí hay un código simple ...

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. Otra aplicación de punteros dobles:
(esto también cubriría el pase por referencia)

Suponga que desea actualizar un personaje desde una función. Si intenta lo siguiente: -

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

La salida será AA. Esto no funciona, ya que ha "Pasado por valor" a la función.

La forma correcta de hacerlo sería:

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

Ahora amplíe este requisito para actualizar una cadena en lugar de caracteres.
Para esto, debe recibir el parámetro en la función como un puntero doble.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

En este ejemplo, el método espera un puntero doble como parámetro para actualizar el valor de una cadena.

Bhavuk Mathur
fuente
#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } Pero también puede hacerlo sin usar el puntero doble.
kumar
" char ** ch - 'ch' contiene la dirección de una matriz de punteros de caracteres " . No, contiene la dirección del primer elemento de una matriz de charpunteros. Un puntero a una matriz de char*se escribiría, por ejemplo, así: se char(*(*p)[42])define pcomo un puntero a una matriz de 42 punteros a char.
alk
El último fragmento está completamente roto. Para empezar: aquí *str = ... strestá desreferenciado sin inicializar invocando un comportamiento indefinido.
alk
Esto malloc(sizeof(char) * 10);no asigna espacio para 10 punteros charsino charsolo para 10 ..
alk
Este bucle for(i=0;i<10;i++) { str = ... falla al usar el índice i.
alk
17

Los punteros a punteros también son útiles como "manejadores" de la memoria donde desea pasar un "manejador" entre las funciones de la memoria reubicable. Básicamente, eso significa que la función puede cambiar la memoria a la que apunta el puntero dentro de la variable del controlador, y cada función u objeto que está utilizando el controlador apuntará correctamente a la memoria recién reubicada (o asignada). A las bibliotecas les gusta hacer esto con tipos de datos "opacos", es decir, tipos de datos en los que no tiene que preocuparse por lo que están haciendo con la memoria apuntada, simplemente pasa el "identificador" entre funciones de la biblioteca para realizar algunas operaciones en esa memoria ...

Por ejemplo:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

Espero que esto ayude,

Jason

Jason
fuente
¿Cuál es la razón por la cual el tipo de identificador no está firmado char **? ¿Vacío ** funcionaría igual de bien?
Connor Clark
55
unsigned charse usa específicamente porque estamos almacenando un puntero a datos binarios que se representarán como bytes sin formato. El uso voidrequerirá un lanzamiento en algún momento, y generalmente no es tan legible como la intención de lo que se está haciendo.
Jason
7

Ejemplo simple que probablemente hayas visto muchas veces antes

int main(int argc, char **argv)

En el segundo parámetro lo tiene: puntero a puntero a char.

Tenga en cuenta que la notación de puntero ( char* c) y la notación de matriz ( char c[]) son intercambiables en argumentos de función. Entonces también podrías escribir char *argv[]. En otras palabras char *argv[]y char **argvson intercambiables.

Lo que representa lo anterior es, de hecho, una serie de secuencias de caracteres (los argumentos de la línea de comandos que se le dan a un programa al inicio).

Consulte también esta respuesta para obtener más detalles sobre la firma de la función anterior.

plats1
fuente
2
"la notación de puntero ( char* c) y la notación de matriz ( char c[]) son intercambiables" (y tienen el mismo significado exacto) en los argumentos de la función . Sin embargo, son diferentes a los argumentos de funciones externas.
pmg
6

Las cadenas son un gran ejemplo de los usos de los punteros dobles. La cadena en sí es un puntero, por lo que cada vez que necesite apuntar a una cadena, necesitará un puntero doble.

Drysdam
fuente
5

Por ejemplo, es posible que desee asegurarse de que cuando libere la memoria de algo, establezca el puntero en nulo después.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

Cuando llamas a esta función, la llamarías con la dirección de un puntero

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

Ahora myMemoryestá configurado en NULL y cualquier intento de reutilizarlo será obviamente un error.

Jeff Foster
fuente
1
debería ser if(*memory)yfree(*memory);
Asha
1
Buen punto, pérdida de señal entre el cerebro y el teclado. Lo he editado para que tenga un poco más de sentido.
Jeff Foster, el
¿Por qué no podemos hacer lo siguiente ... void safeFree (void * memory) {if (memory) {free (memory); memoria = NULL; }}
Peter_pk
@Peter_pk Asignar memoria a nulo no ayudaría porque ha pasado un puntero por valor, no por referencia (de ahí el ejemplo de un puntero a un puntero).
Jeff Foster
2

Por ejemplo, si desea acceso aleatorio a datos no contiguos.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

-- C ª

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Almacena un puntero pque apunta a una matriz de punteros. Cada puntero apunta a una pieza de datos.

Si sizeof(T)es grande, puede que no sea posible asignar un bloque contiguo (es decir, usando malloc) de sizeof(T) * nbytes.

log0
fuente
1
No (!) Necesita emitir el resultado de malloc en C.
alk
2

Una cosa para la que los uso constantemente es cuando tengo una matriz de objetos y necesito realizar búsquedas (búsqueda binaria) en ellos por diferentes campos.
Mantengo la matriz original ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Luego, haga una matriz de punteros ordenados a los objetos.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

Puede hacer tantos arreglos de punteros ordenados como necesite, luego usar una búsqueda binaria en el arreglo de punteros ordenados para acceder al objeto que necesita por los datos que tiene. La matriz original de objetos puede permanecer sin clasificar, pero cada matriz de punteros se ordenará por su campo especificado.

DavidMFrey
fuente
2

¿Por qué dobles punteros?

El objetivo es cambiar lo que apunta el estudiante A, usando una función.

#include <stdio.h>
#include <stdlib.h>


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */
Brian Joseph Spinos
fuente
1
No (!) Es necesario emitir el resultado de malloc en C.
alk
2

El siguiente es un ejemplo muy simple de C ++ que muestra que si desea utilizar una función para establecer un puntero para apuntar a un objeto, necesita un puntero a un puntero . De lo contrario, el puntero seguirá volviendo a nulo .

(Una respuesta de C ++, pero creo que es lo mismo en C.)

(También, para referencia: Google ("pass by value c ++") = "Por defecto, los argumentos en C ++ se pasan por valor. Cuando un argumento se pasa por valor, el valor del argumento se copia en el parámetro de la función").

Por lo tanto, queremos establecer el puntero bigual a la cadena a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

¿Qué pasa en la línea Function_1(&a, b);?

  • El "valor" de &main::a(una dirección) se copia en el parámetro std::string* Function_1::a. Por Function_1::alo tanto, es un puntero a (es decir, la dirección de memoria de) la cadena main::a.

  • El "valor" de main::b(una dirección en la memoria) se copia en el parámetro std::string* Function_1::b. Por lo tanto, ahora hay 2 de estas direcciones en la memoria, ambos punteros nulos. En la línea b = a;, la variable local Function_1::bse cambia a igual Function_1::a(= &main::a), pero la variable main::bno cambia. Después de la llamada a Function_1, main::bsigue siendo un puntero nulo.

¿Qué pasa en la línea Function_2(&a, &b);?

  • El tratamiento de la avariable es el mismo: dentro de la función, Function_2::aes la dirección de la cadena main::a.

  • Pero la variable bahora se pasa como un puntero a un puntero. El "valor" de &main::b(la dirección del puntero main::b ) se copia en std::string** Function_2::b. Por lo tanto, dentro de Function_2, desreferenciando esto como *Function_2::baccederá y modificará main::b. Entonces, la línea *b = a;está configurando main::b(una dirección) igual a Function_2::a(= dirección de main::a) que es lo que queremos.

Si desea utilizar una función para modificar una cosa, ya sea un objeto o una dirección (puntero), debe pasar un puntero a esa cosa. Lo que realmente pasa no puede modificarse (en el alcance de la llamada) porque se realiza una copia local.

(Una excepción es si el parámetro es una referencia, como por ejemplo std::string& a. Pero por lo general son const. Generalmente, si llama f(x), si xes un objeto, debería poder asumir que f no modificará x. Pero si xes un puntero, entonces debería suponga que f podría modificar el objeto señalado por x.)

jt117
fuente
El código C ++ para responder una pregunta en C no es la mejor idea.
alk
1

Un poco tarde para la fiesta, pero espero que esto ayude a alguien.

En las matrices C siempre se asigna memoria en la pila, por lo tanto, una función no puede devolver una matriz (no estática) debido al hecho de que la memoria asignada en la pila se libera automáticamente cuando la ejecución llega al final del bloque actual. Eso es realmente molesto cuando desea lidiar con matrices bidimensionales (es decir, matrices) e implementar algunas funciones que pueden alterar y devolver matrices. Para lograr esto, puede usar un puntero a puntero para implementar una matriz con memoria asignada dinámicamente:

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

Aquí hay una ilustración:

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

El doble puntero a doble puntero A apunta al primer elemento A [0] de un bloque de memoria cuyos elementos son punteros dobles. Puede imaginar estos punteros dobles como las filas de la matriz. Esa es la razón por la cual cada puntero doble asigna memoria para elementos num_cols de tipo double. Además, A [i] apunta a la fila i-ésima, es decir, A [i] apunta a A [i] [0] y ese es solo el primer elemento doble del bloque de memoria para la fila i-ésima. Finalmente, puede acceder al elemento en la i-ésima fila y j-ésima columna fácilmente con A [i] [j].

Aquí hay un ejemplo completo que demuestra el uso:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}
joni
fuente
0

Hoy usé punteros dobles mientras programaba algo para el trabajo, así que puedo responder por qué tuvimos que usarlos (es la primera vez que realmente tuve que usar punteros dobles). Tuvimos que lidiar con la codificación en tiempo real de tramas contenidas en buffers que son miembros de algunas estructuras. En el codificador tuvimos que usar un puntero a una de esas estructuras. El problema era que nuestro puntero estaba siendo cambiado para apuntar a otras estructuras desde otro hilo. Para usar la estructura actual en el codificador, tuve que usar un puntero doble, para apuntar al puntero que se estaba modificando en otro hilo. Al principio no era obvio, al menos para nosotros, que teníamos que adoptar este enfoque. Se imprimieron muchas direcciones en el proceso :)).

DEBE utilizar punteros dobles cuando trabaje en punteros que se cambian en otros lugares de su aplicación. También es posible que los punteros dobles sean imprescindibles cuando se trata de hardware que regresa y se dirige a usted.

Axenie Ionut
fuente
0

Compare el valor de modificación de la variable con el valor de modificación del puntero :

#include <stdio.h>
#include <stdlib.h>

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

Esto me ayudó a evitar el valor de retorno del puntero cuando el puntero fue modificado por la función llamada (utilizada en una lista individualmente vinculada).

VIEJO (malo):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NUEVO (mejor):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Sany
fuente