¿Cómo entiendo las declaraciones de funciones complicadas?

¿Cómo entiendo seguir declaraciones complicadas?

char (*(*f())[])(); char (*(*X[3])())[5]; void (*f)(int,void (*)()); char far *far *ptr; typedef void (*pfun)(int,float); int **(*f)(int**,int**(*)(int **,int **)); 

Como han señalado otros, cdecl es la herramienta adecuada para el trabajo.

Si quieres entender ese tipo de statement sin ayuda de cdecl, intenta leer de adentro hacia afuera y de derecha a izquierda

Tomando un ejemplo al azar de su lista char (*(*X[3])())[5];
Comience en X, que es el identificador que se declara / define (y el identificador interno):

 char (*(*X[3])())[5]; ^ 

X es

 X[3] ^^^ 

X es una matriz de 3

 (*X[3]) ^ /* the parenthesis group the sub-expression */ 

X es una matriz de 3 punteros a

 (*X[3])() ^^ 

X es una matriz de 3 punteros para funcionar aceptando un número de argumentos no especificado (pero fijo)

 (*(*X[3])()) ^ /* more grouping parenthesis */ 

X es una matriz de 3 punteros para funcionar aceptando un número de argumentos no especificado (pero fijo) y devolviendo un puntero

 (*(*X[3])())[5] ^^^ 

X es una matriz de 3 punteros para funcionar aceptando un número de argumentos no especificado (pero fijo) y devolviendo un puntero a una matriz de 5

 char (*(*X[3])())[5]; ^^^^ ^ 

X es una matriz de 3 punteros para funcionar aceptando un número de argumentos no especificado (pero fijo) y devolviendo un puntero a una matriz de 5 caracteres .

Léelo desde adentro, similar a cómo resolverías ecuaciones como {3+5*[2+3*(x+6*2)]}=0 – comenzarías resolviendo lo que hay adentro () luego [] y finalmente {} :

 char (*(*x())[])() ^ 

Esto significa que x es algo .

 char (*(*x())[])() ^^ 

x es una función .

 char (*(*x())[])() ^ 

x devuelve un puntero a algo .

 char (*(*x())[])() ^ ^^^ 

x devuelve un puntero a una matriz .

 char (*(*x())[])() ^ 

x devuelve un puntero a una matriz de punteros .

 char (*(*x())[])() ^ ^^^ 

x devuelve un puntero a una matriz de punteros a funciones

 char (*(*x())[])() ^^^^ 

Es decir, el puntero de matriz devuelto por x puntos a una matriz de punteros de función que apuntan a funciones que devuelven un carácter .

Pero sí, usa cdecl . Lo usé yo mismo para verificar mi respuesta :).

Si esto todavía te confunde (y probablemente debería), intenta hacer lo mismo en un papel o en tu editor de texto favorito. No hay forma de saber lo que significa simplemente mirándolo.

Suena como un trabajo para la herramienta cdecl:

 cdecl> explain char (*(*f())[])(); declare f as function returning pointer to array of pointer to function returning char 

Busqué una página oficial para la herramienta, pero no pude encontrar una que pareciera genuina. En Linux, normalmente puede esperar que su distribución de elección incluya la herramienta, por lo que simplemente la instalé para generar la muestra anterior.

Deberías estar usando la herramienta cdecl . Debería estar disponible en la mayoría de las distribuciones de Linux.

por ejemplo, para esta función, te devolverá:

char (*(*f())[])(); – declara f como una función que devuelve el puntero a la matriz de puntero para devolver la función char

void (*f)(int,void (*)()); – prototipo de puntero de función f. f es una función que toma dos parámetros, el primero es int, y el segundo es un puntero de función para una función que devuelve vacío.

char far *far *ptr; – ptr es un puntero lejano a un puntero lejano (que apunta a un char / byte).

char (*(*X[3])())[5]; – X es una matriz de 3 punteros para funcionar aceptando un número indeterminado de argumentos y devolviendo un puntero a una matriz de 5 caracteres.

typedef void (*pfun)(int,float); – declarando el puntero de la función pfun. pfun es una fuctnion que toma dos parámetros, el primero es int, el segundo es de tipo float. la función no tiene un valor de retorno;

p.ej

 void f1(int a, float b) { //do something with these numbers }; 

Por cierto, las declaraciones complicadas como la última no se ven a menudo. Aquí hay un ejemplo que acabo de inventar para este propósito.

 int **(*f)(int**,int**(*)(int **,int **)); typedef int**(*fptr)(int **,int **); int** f0(int **a0, int **a1) { printf("Complicated declarations and meaningless example!\n"); return a0; } int ** f1(int ** a2, fptr afptr) { return afptr(a2, 0); } int main() { int a3 = 5; int * pa3 = &a3; f = f1; f(&pa3, f0); return 0; } 

Parece que su pregunta real es esta:

¿Cuál es el caso de uso para un puntero a un puntero?

Un puntero a un puntero tiende a aparecer cuando tiene una matriz de algún tipo T, y T en sí es un puntero a otra cosa. Por ejemplo,

  • ¿Qué es una cadena en C? Por lo general, es un char * .
  • ¿Te gustaría una variedad de cadenas de vez en cuando? Por supuesto.
  • ¿Cómo declararías uno? char *x[10] : x es una matriz de 10 punteros a char , también conocido como 10 cadenas.

En este punto, es posible que se pregunte dónde entra char ** . Entra en la imagen de la relación muy estrecha entre punteros aritmética y matrices en C. Un nombre de matriz, x es (casi) siempre convertido en un puntero a su primer elemento .

  • ¿Cuál es el primer elemento? Un char * .
  • ¿Qué es un puntero al primer elemento? Un char ** .

En C, la matriz E1[E2] se define como equivalente a *(E1 + E2) . Normalmente, E1 es el nombre de la matriz, digamos x , que se convierte automáticamente en una char ** , y E2 es un índice, por ejemplo 3. (Esta regla también explica por qué 3[x] x[3] son la misma cosa. )

Los punteros a punteros también aparecen cuando desea una matriz dinámicamente asignada de algún tipo T , que a su vez es un puntero . Para empezar, imaginemos que no sabemos qué tipo de T es.

  • Si queremos un vector dinámicamente asignado de T, ¿qué tipo necesitamos? T *vec .
  • ¿Por qué? Como podemos realizar la aritmética del puntero en C, cualquier T * puede servir como la base de una secuencia contigua de T en la memoria.
  • ¿Cómo asignamos este vector, por ejemplo, de n elementos? vec = malloc(n * sizeof(T)) ;

Esta historia es verdadera para absolutamente cualquier tipo T , y así es cierto para char * .

  • ¿Cuál es el tipo de vec si T es char * ? char **vec .

Los punteros a punteros también aparecen cuando tiene una función que necesita modificar un argumento de tipo T, que a su vez es un puntero .

  • Mire la statement para strtol : strtol long strtol(char *s, char **endp, int b) .
  • ¿De qué se trata todo esto? strtol convierte una cadena de la base b en un entero. Quiere decirte qué tan lejos en la cuerda se puso. Quizás podría devolver una estructura que contenga tanto un carácter long como un char * , pero así no es como se declara.
  • En cambio, devuelve su segundo resultado al pasar la dirección de una cadena que modifica antes de regresar.
  • ¿Qué es una cadena de nuevo? Oh sí, char * .
  • Entonces, ¿cuál es la dirección de una cadena? char ** .

Si recorre este camino lo suficiente, también puede encontrar tipos T *** , aunque casi siempre puede reestructurar el código para evitarlos.

Finalmente, punteros a punteros aparecen en ciertas implementaciones complicadas de listas enlazadas . Considere la statement estándar de una lista doblemente enlazada en C.

 struct node { struct node *next; struct node *prev; /* ... */ } *head; 

Esto funciona bien, aunque no voy a reproducir las funciones de inserción / eliminación aquí, pero tiene un pequeño problema. Cualquier nodo puede eliminarse de la lista (o tener un nuevo nodo insertado antes) sin referencia al encabezado de la lista. Bueno, no exactamente cualquier nodo. Esto no es cierto para el primer elemento de la lista, donde prev será nulo. Esto puede ser moderadamente molesto en algunos tipos de código C donde se trabaja más con los nodos que con la lista como concepto. Esta es una ocurrencia razonablemente común en el código de sistemas de bajo nivel.

¿Qué pasa si reescribimos un node como este?

 struct node { struct node *next; struct node **prevp; /* ... */ } *head; 

En cada nodo, los puntos prevp no están en el nodo anterior, sino en el next puntero de los nodos anteriores. ¿Qué pasa con el primer nodo? prevp puntos prevp en la head . Si dibuja una lista como esta (y debe dibujarla para comprender cómo funciona), verá que puede eliminar el primer elemento o insertar un nuevo nodo antes del primer elemento sin hacer referencia explícita de head .

x: función que devuelve el puntero a la matriz [] del puntero a la función que devuelve char “- ¿eh?

Tienes una función

Esa función devuelve un puntero.

Ese puntero apunta a una matriz.

Esa matriz es una matriz de indicadores de función (o indicadores de funciones)

Esas funciones devuelven char *.

  what's the use case for a pointer to a pointer? 

Una es facilitar los valores de retorno a través de argumentos.

Digamos que tienes

 int function(int *p) *p = 123; return 0; //success ! } 

Lo llamas así

 int x; function(&x); 

Como puede ver, para que la function pueda modificar nuestra x debemos pasarle un puntero a nuestra x.

¿Qué pasa si x no es un int, sino un char * ? Bueno, sigue siendo el mismo, tenemos que pasar un puntero a eso. Un puntero a un puntero:

 int function(char **p) *p = "Hello"; return 0; //success ! } 

Lo llamas así

 char *x; function(&x); 

La respuesta de Remo.D para leer funciones es una buena sugerencia. Aquí hay algunas respuestas para los demás.

Un caso de uso para un puntero a un puntero es cuando desea pasarlo a una función que modificará el puntero. Por ejemplo:

 void foo(char **str, int len) { *str = malloc(len); } 

Además, esto podría ser una matriz de cadenas:

 void bar(char **strarray, int num) { int i; for (i = 0; i < num; i++) printf("%s\n", strarray[i]); } 

Por lo general, no se deben usar declaraciones tan complicadas, aunque a veces se necesitan tipos que sean bastante complicados para cosas como indicadores de función. En esos casos, es mucho más legible usar typedefs para tipos intermedios; por ejemplo:

 typedef void foofun(char**, int); foofun *foofunptr; 

O bien, para su primer ejemplo de "función que devuelve el puntero a la matriz [] del puntero a la función que devuelve char", puede hacer:

 typedef char fun_returning_char(); typedef fun_returning_char *ptr_to_fun; typedef ptr_to_fun array_of_ptrs_to_fun[]; typedef array_of_ptrs_to_fun *ptr_to_array; ptr_to_array myfun() { /*...*/ } 

En la práctica, si estás escribiendo algo sensato, muchas de esas cosas tendrán nombres significativos propios; por ejemplo, estas podrían ser funciones que devuelven nombres (de algún tipo), por lo que fun_returning_char podría ser name_generator_type , y array_of_ptrs_to_fun podría ser name_generator_list . Así que podría colapsarlo un par de líneas, y solo definir esos dos tiposdef, que probablemente sean útiles en cualquier otro lugar en cualquier caso.

 char far *far *ptr; 

Esta es una forma de Microsoft obsoleta, que se remonta a MS-DOS y los primeros días de Windows. La versión CORTA es que este es un puntero lejano a un puntero lejano a un char, donde un puntero lejano puede apuntar a cualquier lugar de la memoria, en oposición a un puntero cercano que solo podría apuntar a cualquier parte del segmento de datos de 64K. Realmente no desea conocer los detalles acerca de los modelos de memoria de Microsoft para trabajar con la architecture de memoria segmentada Intel 80×86 completamente descerebrada.

 typedef void (*pfun)(int,float); 

Esto declara pfun como typedef para un puntero a un procedimiento que toma un int y un float. Normalmente lo usarías en una statement de función o un prototipo, a saber.

 float foo_meister(pfun rabbitfun) { rabbitfun(69, 2.47); } 

Tenemos que evaluar todas las declaraciones de statement de puntero de izquierda a derecha, comenzando desde donde se declara el nombre del puntero o de statement en la statement.

Al evaluar la statement, tenemos que comenzar desde el paréntesis más interno.

Comience con el nombre del puntero o el nombre de la función seguido de los caracteres del extremo derecho en el paréntesis y luego siga los caracteres del extremo izquierdo.

Ejemplo:

 char (*(*f())[])(); ^ char (*(*f())[])(); ^^^ In here f is a function name, so we have to start from that. 

F()

 char (*(*f())[])(); ^ Here there are no declarations on the righthand side of the current parenthesis, we do have to move to the lefthand side and take *: char (*(*f())[])(); ^ f() * 

Hemos completado los caracteres de paréntesis internos, y ahora tenemos que volver a un nivel detrás de esto:

 char (*(*f())[])(); ------ 

Ahora tome [], porque está en el lado derecho del paréntesis actual.

 char (*(*f())[])(); ^^ f() * [] 

Ahora toma el * porque no hay un personaje en el lado derecho.

 char (*(*f())[])(); ^ char (*(*f())[])(); ^ f() * [] * char (*(*f())[])(); 

Luego evalúa el paréntesis externo de apertura y cierre, indica una función.

 f() * [] * () char (*(*f())[])(); 

Ahora podemos agregar el tipo de datos al final de la statement.

 f() * [] * () char. char (*(*f())[])(); 

Respuesta final:

  f() * [] * () char. 

f es una función que devuelve el puntero a la matriz [] de punteros para que funcione devolviendo char.

Olvídate de 1 y 2, esto es solo teórico.

3: Esto se usa en la función de entrada del progtwig int main(int argc, char** argv) . Puede acceder a una lista de cadenas utilizando un char** . argv [0] = primera cuerda, argv [1] = segunda cuerda, …

Pasar un puntero como argumento a una función permite que esa función cambie el contenido de la variable apuntada, lo que puede ser útil para devolver información por medios distintos al valor de retorno de la función. Por ejemplo, el valor de retorno puede ya usarse para indicar error / éxito, o puede querer devolver múltiples valores. La syntax para esto en el código de llamada es foo (& var), que toma la dirección de var, es decir, un puntero a var.

Entonces, como tal, si la variable cuyo contenido desea que cambie la función es un puntero (por ejemplo, una cadena), el parámetro se declarará como un puntero a un puntero .

 #include  char *some_defined_string = "Hello, " ; char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated int point_me_to_the_strings(char **str1, char **str2, char **str3) { *str1 = some_defined_string ; *str2 = alloc_string() ; *str3 = "!!" ; if (str2 != 0) { return 0 ; //successful } else { return -1 ; //error } } main() { char *s1 ; //uninitialized char *s2 ; char *s3 ; int success = point_me_to_the_strings(&s1, &s2, &s3) ; printf("%s%s%s", s1, s2, s3) ; } 

Tenga en cuenta que main () no asigna ningún almacenamiento para las cadenas, por lo que point_me_to_the_strings () no escribe en str1, str2 y str3 como lo haría si se pasaran como punteros a caracteres. Por el contrario, point_me_to_the_strings () cambia los punteros por sí mismos, haciendo que apunten a diferentes lugares, y puede hacerlo porque tiene punteros a ellos.