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 esta 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.
f
como una matriz de punteros a funciones que podría tomar cualquier argumento .. si fueravoid (*(*f[])(void))(void);
, entonces sí, sería funciones que no tienen argumentos ...Respuestas:
Hay una regla llamada "Regla en sentido horario / espiral" para ayudar a encontrar el significado de una declaración compleja.
De c-faq :
Puede consultar el enlace de arriba para ver ejemplos.
También tenga en cuenta que para ayudarlo también hay un sitio web llamado:
http://www.cdecl.org
Puede ingresar una declaración C y le dará su significado en inglés. por
produce:
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 está 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 queint **(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 respuesta por James Kanze (señalado por los hacks en los comentarios).
fuente
La regla de "espiral" se cae de las siguientes reglas de precedencia:
Los operadores de
[]
llamadas de subíndice y función()
tienen mayor prioridad que los unarios*
, 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, entonces necesita agrupar explícitamente el
*
con el identificador, como en(*a)[]
o(*f)()
.Entonces te das cuenta de eso
a
yf
puedes ser expresiones más complicadas que solo identificadores; enT (*a)[N]
,a
podría ser un identificador simple, o podría ser una función llamada 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 facilitaría la lectura de las declaraciones de izquierda a derecha (void f[]*()*();
definitivamente fluye mejor quevoid (*(*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:
La
signal
función en la biblioteca estándar es probablemente el espécimen tipo para este tipo de locura:En este punto, la mayoría de la gente dice "use typedefs", que sin duda es una opció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 revisar los typedefs y descifrar la sintaxis correcta. Por el contrario, la versión "desnuda" es bastante curiosa, pero le dice exactamente cómo usarlaf
en una expresión (es decir(*(*f[i])())();
, suponiendo que ninguna función tome argumentos).fuente
f
árbol de desaceleración, explicando la precedencia ... por alguna razón siempre me gusta el arte ASCII, especialmente cuando se trata de explicar cosas :)void
paréntesis de funciones, de lo contrario puede tomar cualquier argumento.En C, la declaración refleja el uso, así es como se define en el estándar. La declaracion:
Es una afirmación de que la expresión
(*(*f[i])())()
produce un resultado de tipovoid
. Lo que significa:f
debe ser una matriz, ya que puede indexarla:Los elementos de
f
deben ser punteros, ya que puede desreferenciarlos:Esos punteros deben ser punteros a funciones que no toman argumentos, ya que puede llamarlos:
Los resultados de esas funciones también deben ser punteros, ya que puede desreferenciarlos:
Esos punteros también deben ser punteros a funciones que no toman argumentos, ya que puede llamarlos:
Esos punteros de función deben regresar
void
La "regla espiral" es simplemente un mnemónico que proporciona una forma diferente de entender lo mismo.
fuente
vector< function<function<void()>()>* > f
, especialmente si agrega elstd::
s. (Pero bueno, el ejemplo es artificial ... incluso sef :: [IORef (IO (IO ()))]
ve raro.)a[x]
indica que la expresióna[i]
es válida cuandoi >= 0 && i < x
. Mientras que,a[]
deja el tamaño sin especificar, y por lo tanto es idéntico a*a
: indica que la expresióna[i]
(o equivalente*(a + i)
) es válida para algún rango dei
.(*f[])()
es un tipo que puede indexar, luego desreferenciar, luego llamar, por lo que es una matriz de punteros a funciones.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 favorece
[]
y()
supera*
: si*
precede al identificador y lo[]
sigue, el identificador representa una matriz, no un puntero. Del mismo modo, si*
precede al identificador y lo()
sigue, el identificador representa una función, no un puntero. (Los paréntesis siempre se pueden usar para anular la prioridad normal de una[]
y()
otra vez*
).Esta regla en realidad implica zigzaguear de un lado del identificador al otro.
Ahora descifrando una declaración simple
Aplicando regla:
Descifremos la declaración compleja como
aplicando las reglas anteriores:
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 .
fuente
char (x())[5]
debería dar como resultado un error de sintaxis, pero cdecl lo analiza como: declararx
como función que devuelve la matriz 5 dechar
.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 procede "en espiral" generalmente le sugerirá que alterne entre matrices y punteros en la declaración
int ***foo[][][]
cuando en realidad todos los niveles de la matriz van antes que cualquiera de los niveles de puntero.fuente
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.
fuente
Como un factor de trivia aleatorio, puede resultarle divertido saber que hay una palabra real en inglés para describir cómo se leen las declaraciones C: Boustrofedónicamente , es decir, alternando de derecha a izquierda con de izquierda a derecha.
Referencia: Van der Linden, 1994 - Página 76
fuente
Con respecto a la utilidad de esto, cuando trabajas con shellcode ves mucho esta construcción:
Si bien no es tan sintácticamente complicado, este patrón en particular aparece mucho.
Un ejemplo más completo en esta 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.
fuente
La declaracion
es solo una forma oscura de decir
con
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
.fuente
Encontré que el método descrito por Bruce Eckel es útil y fácil de seguir:
Tomado de: Pensando en C ++ Volumen 1, segunda edición, capítulo 3, sección "Direcciones de funciones" por Bruce Eckel.
fuente
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 para 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:
fuente
IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
y se analiza comoif (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
.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 alumnos y colegas leer las declaraciones 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:
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:
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" :)
fuente
(*(*f[]) ()) ()
Resolviendo
void
>>(*(*f[]) ())
() = nuloResoiving
()
>>(*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 que regresa (puntero a (función que regresa (nulo))))Resolviendo
[ ]
>>fuente