Punteros como argumentos de función en C

Si tuviera este código, por ejemplo:

int num = 5; int *ptr = # 

¿Cuál es la diferencia entre las dos funciones siguientes?

 void func(int **foo); void func(int *foo); 

Donde llamo a la función:

 func(&ptr); 

Me doy cuenta de que el primero de los dos toma un puntero a un puntero como parámetro, mientras que el segundo solo toma un puntero.

Si paso en func(&ptr) , estoy efectivamente pasando un puntero. ¿Qué diferencia hace que el puntero apunte a otro puntero?

Creo que este último dará una advertencia de incompatibilidad, pero parece que los detalles no importan siempre que sepas lo que estás haciendo. Parece que tal vez por el bien de la legibilidad y la comprensión de la primera es una mejor opción (puntero de 2 estrellas), pero desde un punto de vista lógico, ¿cuál es la diferencia?

Una regla de oro razonable es que no se puede cambiar exactamente lo que se transfiere exactamente de tal manera que la persona que llama ve el cambio. Pasar punteros es la solución.

Pase por valor: void fcn(int foo)

Cuando pasa por valor, obtiene una copia del valor. Si cambia el valor en su función, la persona que llama aún ve el valor original independientemente de sus cambios.

Pase por el puntero al valor: void fcn(int* foo)

Al pasar por el puntero se obtiene una copia del puntero: apunta a la misma ubicación de memoria que el original. Esta ubicación de memoria es donde se almacena el original. Esto le permite cambiar el valor apuntado. Sin embargo, no puede cambiar el puntero real a los datos ya que solo recibió una copia del puntero.

Pase el puntero al puntero al valor: void fcn(int** foo)

Usted obtiene alrededor de lo anterior pasando un puntero a un puntero a un valor. Como se indicó anteriormente, puede cambiar el valor para que la persona que llama vea el cambio porque es la misma ubicación de memoria que usa el código de la persona que llama. Por el mismo motivo, puede cambiar el puntero al valor. Esto le permite hacer cosas tales como asignar memoria dentro de la función y devolverla; &arg2 = calloc(len); . Aún no puede cambiar el puntero al puntero, ya que de eso es de lo que recibe una copia.

La diferencia se dice simplemente en las operaciones con las que el procesador manejará el código. el valor en sí mismo es solo una dirección en ambos casos, eso es cierto. Pero a medida que la dirección se desreferencia, es importante para el procesador y también para el comstackdor, saber después de la desreferenciación, con qué se manejará.

Si tuviera este código, por ejemplo:

 int num = 5; int *ptr = # 

¿Cuál es la diferencia entre las dos funciones siguientes ?:

 void func(int **foo); void func(int *foo); 

El primero quiere un puntero a un puntero a un int, el segundo quiere un puntero que apunta directamente a un int.

Donde llamo a la función:

 func(&ptr); 

Como ptr es un puntero a int, &ptr es una dirección, compatible con int ** .

La función que toma un int * hará algo diferente como con int ** . El resultado de la conversación será completamente diferente, lo que dará lugar a un comportamiento indefinido, lo que podría causar un colapso.

Si paso en func (& ptr) estoy efectivamente pasando un puntero. ¿Qué diferencia hace que el puntero apunte a otro puntero?

  +++++++++++++++++++ adr1 (ptr): + adr2 + +++++++++++++++++++ +++++++++++++++++++ adr2 (num): + 42 + +++++++++++++++++++ 

En adr2 , tenemos un valor int, 42.

En adr1 , tenemos la dirección adr2 , que tiene el tamaño de un puntero.

&ptr nos da adr1, ptr , tiene el valor de &num , que es adr2.

Si uso adr1 como int * , adr2 tratará adr2 como un entero, lo que dará lugar a un número (posiblemente bastante grande).

Si utilizo adr2 como int ** , la primera desreferencia lleva a 42, lo que se malinterpretará como una dirección y posiblemente haga que el progtwig falle.

Es más que solo una óptica para diferenciar entre int * e int ** .

Creo que este último dará una advertencia de incompatibilidad,

… que tiene un significado …

pero parece que los detalles no importan mientras sepas lo que estás haciendo.

¿Vos si?

Parece que tal vez por el bien de la legibilidad y la comprensión de la primera es una mejor opción (puntero de 2 estrellas), pero desde un punto de vista lógico, ¿cuál es la diferencia?

Depende de lo que hace la función con el puntero.

Hay dos principales diferencias prácticas:

  1. Pasar un puntero a un puntero permite que la función modifique el contenido de ese puntero de forma que la persona que llama pueda ver. Un ejemplo clásico es el segundo argumento para strtol() . Después de una llamada a strtol() , el contenido de ese puntero debe apuntar al primer carácter de la cadena que no fue analizado para calcular el valor long . Si acaba de pasar el puntero a strtol() , cualquier cambio que realice será local, y sería imposible informarle a la persona que llama cuál era la ubicación. Al pasar la dirección de ese puntero, strtol() puede modificarlo de una manera que la persona que llama puede ver. Es como pasar la dirección de cualquier otra variable.

  2. Más fundamentalmente, el comstackdor necesita saber el tipo al que se apunta para desreferenciar. Por ejemplo, al desreferenciar un double * , el comstackdor interpretará (en una implementación donde el double consume 8 bytes) los 8 bytes que comienzan en la ubicación de la memoria como el valor del doble. Pero, en una implementación de 32 bits, al desreferenciar un double ** , el comstackdor interpretará los 4 bytes comenzando en esa ubicación como la dirección de otro doble. Al desreferenciar un puntero, el tipo al que se apunta es la única información que el comstackdor tiene sobre cómo interpretar los datos en esa dirección, por lo que conocer el tipo exacto es crítico, y es por eso que sería un error pensar “son todos solo apuntadores, ¿cuál es la diferencia “?

En general, la diferencia indica que la función se asignará al puntero, y que esta asignación no debe ser solo local para la función. Por ejemplo (y tenga en cuenta que estos ejemplos tienen el propósito de examinar la naturaleza de foo y no completar funciones, así como el código en su publicación original se supone que es código de trabajo real):

 void func1 (int *foo) { foo = malloc (sizeof (int)); } int a = 5; func1 (&a); 

Es parecido a

 void func2 (int foo) { foo = 12; } int b = 5; func2 (b); 

En el sentido de que foo puede igualar 12 en func2 (), pero cuando func2 () retorna, b igual será 5. En func1 (), foo apunta a un nuevo int, pero a sigue siendo a cuando devuelve func1 ().

¿Qué pasa si queremos cambiar el valor de a o b ? WRT b , una int normal:

 void func3 (int *foo) { *foo = 12; } int b = 5; func2 (&b); 

Funcionará: tenga en cuenta que necesitamos un puntero a un int. Para cambiar el valor en un puntero (es decir, la dirección del int al que apunta, y no solo el valor en el int al que apunta):

 void func4 (int **foo) { *foo = malloc (sizeof (int)); } int *a; foo (&a); 

‘a’ ahora apunta a la memoria devuelta por malloc en func4 (). La dirección &a es la dirección de a , un puntero a un int . Un puntero int contiene la dirección de un int. func4() toma la dirección de un puntero int para que pueda poner la dirección de un int en esta dirección, así como func3 () toma la dirección de un int para que pueda poner un nuevo valor int en él.

Así es como se usan los diferentes estilos de argumento.

Ha pasado un tiempo desde que se hizo esta pregunta, pero aquí está mi opinión sobre esto. Ahora estoy tratando de aprender C y los punteros son interminablemente confusos … Así que me estoy tomando este tiempo para aclarar los punteros en punteros, al menos para mí. Así es como lo pienso.
He tomado un ejemplo de aquí :

 #include  #include  int allocstr(int len, char **retptr) { char *p = malloc(len + 1); /* +1 for \0 */ if(p == NULL) return 0; *retptr = p; return 1; } int main() { char *string = "Hello, world!"; char *copystr; if(allocstr(strlen(string), &copystr)) strcpy(copystr, string); else fprintf(stderr, "out of memory\n"); return 0; } 

Me preguntaba por qué allocstr necesita un doble puntero. Si es un puntero, significa que puede pasarlo y se cambiará después de regresar …
Si haces este ejemplo, funciona bien. Pero si cambia el allocstr para tener solo * puntero en lugar de ** puntero (y copystr en lugar de & copystr en main) obtendrá un error de segmentación . ¿Por qué? Puse algunos printfs en el código y funciona bien hasta la línea con strcpy . Así que supongo que no asignó memoria para copystr . Nuevamente, ¿por qué?
Volvamos a lo que significa pasar por el puntero. Significa que pasas la ubicación de la memoria y puedes escribir directamente allí el valor que deseas. Puede modificar el valor porque tiene acceso a la ubicación de memoria de su valor.
De forma similar, cuando pasa un puntero a un puntero, pasa la ubicación de memoria de su puntero, es decir, la ubicación de memoria de la ubicación de su memoria. Y ahora (puntero a puntero) puede cambiar la ubicación de la memoria ya que podría cambiar el valor cuando estaba usando solo un puntero.
La razón por la que funciona el código es que pasas la dirección de una ubicación de memoria. La función allocstr cambia el tamaño de esa ubicación de memoria para que pueda contener “Hello world!” y devuelve un puntero a esa ubicación de memoria.
En realidad es lo mismo que pasar un puntero, pero en lugar de un valor tenemos una ubicación de memoria.