pointer - ptr function c
C no es tan difĂcil: nulo(*(* f[])())() (13)
¿Entonces esta "lectura en espiral" es algo válido?
La aplicación de la regla espiral o el uso de cdecl no son válidos siempre. Ambos fallan en algunos casos. La regla espiral funciona para muchos casos, pero no es universal .
Para descifrar declaraciones complejas recuerde estas dos reglas simples:
-
Siempre lea las declaraciones de adentro hacia afuera : comience desde el paréntesis más interno, si lo hay. Localice el identificador que se declara y comience a descifrar la declaración desde allí.
-
Cuando hay una opción, siempre favorezca
[]
y()
sobre*
: Si*
precede al identificador y[]
sigue, el identificador representa una matriz, no un puntero. Del mismo modo, si*
precede al identificador y()
sigue, el identificador representa una función, no un puntero. (Los paréntesis siempre se pueden usar para anular la prioridad normal de[]
y()
sobre*
.)
Esta regla en realidad implica zigzaguear de un lado del identificador al otro.
Ahora descifrando una declaración simple
int *a[10];
Aplicando regla:
int *a[10]; "a is"
^
int *a[10]; "a is an array"
^^^^
int *a[10]; "a is an array of pointers"
^
int *a[10]; "a is an array of pointers to `int`".
^^^
Descifremos la declaración compleja como
void ( *(*f[]) () ) ();
aplicando las reglas anteriores:
void ( *(*f[]) () ) (); "f is"
^
void ( *(*f[]) () ) (); "f is an array"
^^
void ( *(*f[]) () ) (); "f is an array of pointers"
^
void ( *(*f[]) () ) (); "f is an array of pointers to function"
^^
void ( *(*f[]) () ) (); "f is an array of pointers to function returning pointer"
^
void ( *(*f[]) () ) (); "f is an array of pointers to function returning pointer to function"
^^
void ( *(*f[]) () ) (); "f is an array of pointers to function returning pointer to function returning `void`"
^^^^
Aquí hay un GIF que muestra cómo te va (haz clic en la imagen para ampliarla):
Las reglas mencionadas aquí están tomadas del libro C Programming A Modern Approach de KN KING .
Acabo de ver una foto hoy y creo que agradecería las explicaciones. Así que aquí está la imagen:
Esto me pareció confuso y me pregunté si tales códigos son prácticos. Busqué en Google la imagen y encontré otra imagen en this entrada de reddit, y aquí está esa imagen:
¿Entonces esta "lectura en espiral" es algo válido?
¿Es así como analizan los compiladores de C?
Sería genial si hubiera explicaciones más simples para este código extraño.
Además de todo, ¿pueden ser útiles este tipo de códigos?
Si es así, ¿dónde y cuándo?
Hay una pregunta sobre la "regla espiral", pero no solo estoy preguntando cómo se aplica o cómo se leen las expresiones con esa regla. También cuestiono el uso de tales expresiones y la validez de la regla espiral. Con respecto a esto, algunas buenas respuestas ya están publicadas.
Recuerde que estas reglas para C declaran
Y la precedencia nunca estará en duda:
Comience con el sufijo, continúe con el prefijo,
Y lea ambos conjuntos desde adentro hacia afuera.
- yo, a mediados de los 80
Excepto por modificaciones entre paréntesis, por supuesto. Y tenga en cuenta que la sintaxis para declarar esto refleja exactamente la sintaxis para usar esa variable para obtener una instancia de la clase base.
En serio, esto no es difícil de aprender de un vistazo; solo tienes que estar dispuesto a pasar un tiempo practicando la habilidad. Si va a mantener o adaptar el código C escrito por otras personas, definitivamente vale la pena invertir ese tiempo. También es un truco de fiesta divertido para enloquecer a otros programadores que no lo han aprendido.
Para su propio código: como siempre, el hecho de que algo se pueda escribir como una línea no significa que deba serlo, a menos que sea un patrón extremadamente común que se haya convertido en un idioma estándar (como el bucle de copia de cadena) . Usted y aquellos que lo siguen serán mucho más felices si construye tipos complejos a partir de tipos de letra en capas y desreferencias paso a paso en lugar de confiar en su capacidad de generar y analizar estos "de una sola vez". El rendimiento será igual de bueno, y la legibilidad y la facilidad de mantenimiento del código serán tremendamente mejores.
Podría ser peor, ya sabes. Hubo una declaración legal PL / I que comenzó con algo como:
if if if = then then then = else else else = if then ...
Como un hecho trivia aleatorio, puede resultarle divertido saber que hay una palabra real en inglés para describir cómo se leen las declaraciones C: Boustrophedonically , es decir, alternando de derecha a izquierda con de izquierda a derecha.
Referencia: Van der Linden, 1994 - Página 76
Con respecto a la utilidad de esto, cuando trabajas con shellcode ves mucho esta construcción:
int (*ret)() = (int(*)())code;
ret();
Si bien no es tan sintácticamente complicado, este patrón en particular aparece mucho.
Un ejemplo más completo en this pregunta SO.
Entonces, aunque la utilidad en la medida de la imagen original es cuestionable (sugeriría que cualquier código de producción se simplifique drásticamente), hay algunas construcciones sintácticas que surgen bastante.
Dudo que construcciones como esta puedan tener algún uso en la vida real. Incluso los detesto como preguntas de entrevista para los desarrolladores habituales (probablemente aceptables para los escritores de compiladores). typedefs debería usarse en su lugar.
En C, la declaración refleja el uso, así es como se define en el estándar. La declaracion:
void (*(*f[])())()
Es una afirmación de que la expresión
(*(*f[i])())()
produce un resultado de tipo
void
.
Lo que significa:
-
f
debe ser una matriz, ya que puede indexarla:f[i]
-
Los elementos de
f
deben ser punteros, ya que puede desreferenciarlos:*f[i]
-
Esos punteros deben ser punteros a funciones que no toman argumentos, ya que puede llamarlos:
(*f[i])()
-
Los resultados de esas funciones también deben ser punteros, ya que puede desreferenciarlos:
*(*f[i])()
-
Esos punteros también deben ser punteros a funciones que no toman argumentos, ya que puede llamarlos:
(*(*f[i])())()
-
Esos punteros de función deben devolver
void
La "regla espiral" es simplemente un mnemónico que proporciona una forma diferente de entender lo mismo.
Encontré que el método descrito por Bruce Eckel es útil y fácil de seguir:
Definir un puntero de función
Para definir un puntero a una función que no tiene argumentos ni valor de retorno, usted dice:
void (*funcPtr)();
Cuando está buscando una definición compleja como esta, la mejor manera de atacarla es comenzar en el medio y salir. "Comenzar en el medio" significa comenzar con el nombre de la variable, que es funcPtr. "Trabajar para salir" significa buscar a la derecha el elemento más cercano (nada en este caso; el paréntesis derecho lo detiene), luego mirar a la izquierda (un puntero indicado por el asterisco), luego mirar a la derecha (un lista de argumentos vacía que indica una función que no toma argumentos), luego mirando hacia la izquierda (nula, lo que indica que la función no tiene valor de retorno). Este movimiento derecha-izquierda-derecha funciona con la mayoría de las declaraciones.
Para revisar, "comience en el medio" ("funcPtr es un ..."), vaya a la derecha (nada allí, está parado por el paréntesis derecho), vaya a la izquierda y encuentre el ''*'' (" ... apunta a un ... "), ve a la derecha y encuentra la lista de argumentos vacía (" ... función que no toma argumentos ... "), ve a la izquierda y encuentra el vacío (" funcPtr es un puntero a una función que no toma argumentos y devuelve nulo ").
Quizás se pregunte por qué * funcPtr requiere paréntesis. Si no los usó, el compilador vería:
void *funcPtr();
Estaría declarando una función (que devuelve un vacío *) en lugar de definir una variable. Puede pensar en el compilador como si estuviera pasando por el mismo proceso que usted cuando se da cuenta de lo que se supone que es una declaración o definición. Necesita esos paréntesis para "chocar contra", así que vuelve a la izquierda y encuentra el ''*'', en lugar de continuar a la derecha y encontrar la lista de argumentos vacía.
Declaraciones y definiciones complicadas
Por otro lado, una vez que descubra cómo funciona la sintaxis de declaración C y C ++, puede crear elementos mucho más complicados. Por ejemplo:
//: C03:ComplicatedDefinitions.cpp /* 1. */ void * (*(*fp1)(int))[10]; /* 2. */ float (*(*fp2)(int,int,float))(int); /* 3. */ typedef double (*(*(*fp3)())[10])(); fp3 a; /* 4. */ int (*(*f4())[10])(); int main() {} ///:~
Camine a través de cada uno y use la guía derecha-izquierda para resolverlo. El número 1 dice "fp1 es un puntero a una función que toma un argumento entero y devuelve un puntero a una matriz de 10 punteros vacíos".
El número 2 dice "fp2 es un puntero a una función que toma tres argumentos (int, int y float) y devuelve un puntero a una función que toma un argumento entero y devuelve un float".
Si está creando muchas definiciones complicadas, es posible que desee utilizar un typedef. El número 3 muestra cómo un typedef guarda escribiendo la complicada descripción cada vez. Dice "Un fp3 es un puntero a una función que no toma argumentos y devuelve un puntero a una matriz de 10 punteros a funciones que no toman argumentos y devuelven dobles". Luego dice "a es uno de estos tipos de fp3". Typedef generalmente es útil para construir descripciones complicadas a partir de simples.
El número 4 es una declaración de función en lugar de una definición de variable. Dice "f4 es una función que devuelve un puntero a una matriz de 10 punteros a funciones que devuelven enteros".
Rara vez, si alguna vez, necesitará declaraciones y definiciones tan complicadas como estas. Sin embargo, si realiza el ejercicio de resolverlos, ni siquiera se sentirá levemente molesto con los ligeramente complicados que puede encontrar en la vida real.
Es solo una "espiral" porque en esta declaración solo hay un operador en cada lado dentro de cada nivel de paréntesis.
Afirmar que procedes "en espiral" generalmente sugiere que alternes entre matrices y punteros en la declaración
int ***foo[][][]
cuando en realidad todos los niveles de matriz van antes que cualquiera de los niveles de puntero.
Hay una regla llamada "Regla en sentido horario / espiral" para ayudar a encontrar el significado de una declaración compleja.
De c-faq :
Hay tres pasos simples a seguir:
Comenzando con el elemento desconocido, muévase en una espiral / sentido horario; al encontrar los siguientes elementos, reemplácelos con las declaraciones en inglés correspondientes:
[X]
o[]
=> Tamaño de matriz X de ... o Tamaño de matriz indefinido de ...
(type1, type2)
=> función pasando tipo1 y tipo2 regresando ...
*
=> puntero (s) a ...Siga haciendo esto en espiral / sentido horario hasta que se hayan cubierto todas las fichas.
¡Siempre resuelva cualquier cosa entre paréntesis primero!
Puede consultar el enlace de arriba para ver ejemplos.
También tenga en cuenta que para ayudarlo también hay un sitio web llamado:
Puede ingresar una declaración C y le dará su significado en inglés. por
void (*(*f[])())()
produce:
declara f como matriz de puntero para funcionar regresando puntero a función regresando vacío
EDITAR:
Como se señaló en los comentarios de
Random832
, la regla espiral no aborda la matriz de matrices y dará lugar a un resultado incorrecto en (la mayoría de) esas declaraciones.
Por ejemplo para
int **x[1][2];
la regla espiral ignora el hecho de que
[]
tiene mayor prioridad sobre
*
.
Cuando se encuentra frente a una matriz de matrices, primero se pueden agregar paréntesis explícitos antes de aplicar la regla espiral.
Por ejemplo:
int **x[1][2];
es lo mismo que
int **(x[1][2]);
(también válido C) debido a la precedencia y la regla espiral lo lee correctamente como "x es una matriz 1 de la matriz 2 de puntero a puntero a int", que es la declaración correcta en inglés.
Tenga en cuenta que este problema también ha sido cubierto en esta answer por James Kanze (señalado por los haccks en los comentarios).
La declaracion
void (*(*f[])())()
es solo una forma oscura de decir
Function f[]
con
typedef void (*ResultFunction)();
typedef ResultFunction (*Function)();
En la práctica, se necesitarán nombres más descriptivos en lugar de
ResultFunction
y
Function
.
Si es posible, también especificaría las listas de parámetros como
void
.
La regla de "espiral" se cae de las siguientes reglas de precedencia:
T *a[] -- a is an array of pointer to T
T (*a)[] -- a is a pointer to an array of T
T *f() -- f is a function returning a pointer to T
T (*f)() -- f is a pointer to a function returning T
El subíndice
[]
y los operadores de llamada a función
()
tienen mayor prioridad que el unario
*
, por lo que
*f()
se analiza como
*(f())
y
*a[]
se analiza como
*(a[])
.
Entonces, si desea un puntero a una matriz o un puntero a una función, debe agrupar explícitamente el
*
con el identificador, como en
(*a)[]
o
(*f)()
.
Entonces te das cuenta de que
f
pueden ser expresiones más complicadas que solo identificadores;
en
T (*a)[N]
,
a
podría ser un identificador simple, o podría ser una llamada de función como
(*f())[N]
(
a
->
f()
), o podría ser una matriz como
(*p[M])[N]
, (
a
->
p[M]
), o podría ser una matriz de punteros a funciones como
(*(*p[M])())[N]
(
a
- >
(*p[M])()
), etc.
Sería bueno si el operador de indirección
*
fuera postfix en lugar de unario, lo que haría que las declaraciones fueran algo más fáciles de leer de izquierda a derecha (
void f[]*()*();
definitivamente fluye mejor que
void (*(*f[])())()
), pero no lo es.
Cuando encuentre una declaración peluda como esa, comience por encontrar el identificador más a la izquierda y aplique las reglas de precedencia anteriores, aplicándolas recursivamente a cualquier parámetro de función:
f -- f
f[] -- is an array
*f[] -- of pointers ([] has higher precedence than *)
(*f[])() -- to functions
*(*f[])() -- returning pointers
(*(*f[])())() -- to functions
void (*(*f[])())(); -- returning void
La función de
signal
en la biblioteca estándar es probablemente el tipo de muestra para este tipo de locura:
signal -- signal
signal( ) -- is a function with parameters
signal( sig, ) -- sig
signal(int sig, ) -- which is an int and
signal(int sig, func ) -- func
signal(int sig, *func ) -- which is a pointer
signal(int sig, (*func)(int)) -- to a function taking an int
signal(int sig, void (*func)(int)) -- returning void
*signal(int sig, void (*func)(int)) -- returning a pointer
(*signal(int sig, void (*func)(int)))(int) -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int); -- and returning void
En este punto, la mayoría de la gente dice "use typedefs", que sin duda es una opción:
typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);
innerfunc *f[N];
Pero...
¿Cómo
usarías
f
en una expresión?
Sabes que es un conjunto de punteros, pero ¿cómo lo usas para ejecutar la función correcta?
Tienes que repasar los typedefs y descifrar la sintaxis correcta.
Por el contrario, la versión "desnuda" es bastante curiosa, pero le dice exactamente cómo
usar
f
en una expresión (a saber,
(*(*f[i])())();
suponiendo que ninguna función tome argumentos).
Soy el autor original de la regla espiral que escribí hace tantos años (cuando tenía mucho cabello :) y me honró cuando se agregó al cfaq.
Escribí la regla espiral como una forma de facilitar a mis estudiantes y colegas leer las declaraciones de C "en su cabeza"; es decir, sin tener que usar herramientas de software como cdecl.org, etc. Nunca fue mi intención declarar que la regla espiral sea la forma canónica de analizar las expresiones C. Sin embargo, estoy encantada de ver que la regla ha ayudado literalmente a miles de estudiantes y profesionales de programación de C a lo largo de los años.
Para el registro,
Se ha identificado "correctamente" numerosas veces en muchos sitios, incluso por Linus Torvalds (alguien a quien respeto inmensamente), que hay situaciones en las que mi regla espiral "se rompe". El ser más común:
char *ar[10][10];
Como señalaron otros en este hilo, la regla podría actualizarse para decir que cuando encuentre matrices, simplemente consuma todos los índices como si estuvieran escritos como:
char *(ar[10][10]);
Ahora, siguiendo la regla espiral, obtendría:
"ar es una matriz bidimensional de 10x10 de punteros a char"
¡Espero que la regla espiral continúe siendo útil para aprender C!
PD:
Me encanta la imagen "C no es difícil" :)
-
nulo
(*(*f[]) ()) ()
Resolviendo
void
>>
-
(*(*f[]) ())
() = nulo
Resoiving
()
>>
-
(*
(*f[]) ()
) = función que regresa (nula)
Resolviendo
*
>>
-
(*f[])
() = puntero a (función que regresa (nula))
Resolviendo
()
>>
-
(*
f[]
) = función que regresa (puntero a (función que regresa (nulo)))
Resolviendo
*
>>
-
f
[] = puntero a (función de retorno (puntero a (función de retorno (nulo))))
Resolviendo
[ ]
>>
- f = matriz de (puntero a (función de retorno (puntero a (función de retorno (nulo)))))