Je viens de voir une photo aujourd'hui et je pense que j'apprécierais des explications. Voici donc l'image:
J'ai trouvé cela déroutant et je me suis demandé si de tels codes étaient jamais pratiques. J'ai googlé l'image et j'ai trouvé une autre image dans cette entrée reddit, et voici cette image:
Donc cette "lecture en spirale" est quelque chose de valable? Est-ce ainsi que les compilateurs C analysent?
Ce serait génial s'il y avait des explications plus simples pour ce code étrange.
En dehors de tout, ce genre de codes peut-il être utile? Si oui, où et quand?
Il y a une question sur la "règle en spirale", mais je ne demande pas seulement comment elle est appliquée ou comment les expressions sont lues avec cette règle. Je remets également en question l'utilisation de telles expressions et la validité de la règle en spirale. À ce sujet, de belles réponses sont déjà publiées.
f
comme un tableau de pointeurs vers des fonctions qui pourraient prendre n'importe quel argument .. si c'était le casvoid (*(*f[])(void))(void);
, alors oui, ce seraient des fonctions qui ne prennent aucun argument ...Réponses:
Il existe une règle appelée «Règle dans le sens des aiguilles d'une montre / spirale» pour aider à trouver la signification d'une déclaration complexe.
De c-faq :
Vous pouvez consulter le lien ci-dessus pour des exemples.
Notez également que pour vous aider, il existe également un site Web appelé:
http://www.cdecl.org
Vous pouvez entrer une déclaration C et elle donnera sa signification en anglais. Pour
void (*(*f[])())()
il sort:
ÉDITER:
Comme indiqué dans les commentaires de Random832 , la règle de la spirale ne concerne pas les tableaux de tableaux et conduira à un résultat erroné dans (la plupart de) ces déclarations. Par exemple,
int **x[1][2];
la règle en spirale ignore le fait qui[]
a une priorité plus élevée sur*
.Devant un tableau de tableaux, on peut d'abord ajouter des parenthèses explicites avant d'appliquer la règle de spirale. Par exemple:
int **x[1][2];
est le même queint **(x[1][2]);
(également valide C) en raison de la priorité et la règle de spirale le lit alors correctement comme "x est un tableau 1 du tableau 2 du pointeur vers le pointeur vers int" qui est la déclaration anglaise correcte.Notez que ce problème a également été couvert dans cette réponse par James Kanze (signalé par des haccks dans les commentaires).
la source
La règle de type "spirale" ne fait pas partie des règles de priorité suivantes:
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
Les opérateurs d’
[]
appel d’ indice et de fonction()
ont une priorité plus élevée que l’unaire*
, ils*f()
sont donc analysés en tant que*(f())
et*a[]
sont analysés en tant que*(a[])
.Donc, si vous voulez un pointeur vers un tableau ou un pointeur vers une fonction, vous devez regrouper explicitement le
*
avec l'identificateur, comme dans(*a)[]
ou(*f)()
.Ensuite, vous vous en rendez compte
a
et ilf
peut s'agir d'expressions plus compliquées que de simples identifiants; inT (*a)[N]
,a
pourrait être un simple identifiant, ou un appel de fonction comme(*f())[N]
(a
->f()
), ou un tableau comme(*p[M])[N]
, (a
->p[M]
), ou ce pourrait être un tableau de pointeurs vers des fonctions comme(*(*p[M])())[N]
(a
->(*p[M])()
), etc.Ce serait bien si l'opérateur d'indirection
*
était postfix au lieu d'unaire, ce qui rendrait les déclarations un peu plus faciles à lire de gauche à droite (void f[]*()*();
s'écoule certainement mieux quevoid (*(*f[])())()
), mais ce n'est pas le cas.Lorsque vous rencontrez une déclaration poilue comme celle-ci, commencez par trouver l' identifiant le plus à gauche et appliquez les règles de priorité ci-dessus, en les appliquant de manière récursive à tous les paramètres de fonction:
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
signal
fonction de la bibliothèque standard est probablement le spécimen type de ce genre de folie: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
À ce stade, la plupart des gens disent "utiliser des typedefs", ce qui est certainement une option:
typedef void outerfunc(void); typedef outerfunc *innerfunc(void); innerfunc *f[N];
Mais...
Comment utiliseriez- vous
f
dans une expression? Vous savez que c'est un tableau de pointeurs, mais comment l'utilisez-vous pour exécuter la fonction correcte? Vous devez passer en revue les typedefs et trouver la syntaxe correcte. En revanche, la version "nue" est assez obsédante, mais elle vous indique exactement comment utiliserf
dans une expression (à savoir,(*(*f[i])())();
en supposant qu'aucune des fonctions n'accepte d'arguments).la source
f
arbre de décélération, expliquant la priorité ... pour une raison quelconque, je prends toujours un coup de pied dans l'art ASCII, surtout quand il s'agit d'expliquer les choses :)void
entre parenthèses les fonctions, sinon elle peut prendre n'importe quel argument.En C, la déclaration reflète l'utilisation - c'est ainsi qu'elle est définie dans la norme. La déclaration:
void (*(*f[])())()
Est une assertion que l'expression
(*(*f[i])())()
produit un résultat de typevoid
. Ce qui signifie:f
doit être un tableau, car vous pouvez l'indexer:Les éléments de
f
doivent être des pointeurs, car vous pouvez les déréférencer:Ces pointeurs doivent être des pointeurs vers des fonctions ne prenant aucun argument, car vous pouvez les appeler:
Les résultats de ces fonctions doivent également être des pointeurs, car vous pouvez les déréférencer:
Ces pointeurs doivent également être des pointeurs vers des fonctions ne prenant aucun argument, puisque vous pouvez les appeler:
Ces pointeurs de fonction doivent retourner
void
La «règle de la spirale» est juste un mnémonique qui fournit une manière différente de comprendre la même chose.
la source
vector< function<function<void()>()>* > f
, surtout si vous ajoutez lestd::
s. (Mais bon, l'exemple est artificiel ... a même l'f :: [IORef (IO (IO ()))]
air bizarre.)a[x]
indique que l'expressiona[i]
est valide quandi >= 0 && i < x
. Alors que,a[]
laisse la taille non spécifiée, et est donc identique à*a
: cela indique que l'expressiona[i]
(ou de manière équivalente*(a + i)
) est valide pour une plage dei
.(*f[])()
est un type que vous pouvez indexer, puis déréférencer, puis appeler, c'est donc un tableau de pointeurs vers des fonctions.L'application d'une règle de spirale ou l'utilisation de cdecl ne sont pas toujours valides. Les deux échouent dans certains cas. La règle en spirale fonctionne dans de nombreux cas, mais elle n'est pas universelle .
Pour déchiffrer des déclarations complexes, rappelez-vous ces deux règles simples:
Lisez toujours les déclarations de l'intérieur vers l'extérieur : commencez par la parenthèse la plus profonde, le cas échéant. Localisez l'identifiant déclaré et commencez à déchiffrer la déclaration à partir de là.
Quand il y a un choix, toujours privilégier
[]
et()
plus*
: Si*
précède l'identifiant et le[]
suit, l'identifiant représente un tableau, pas un pointeur. De même, si*
précède l'identifiant et le()
suit, l'identifiant représente une fonction, pas un pointeur. (Les parenthèses peuvent toujours être utilisées pour remplacer la priorité normale de[]
et()
plus*
.)Cette règle implique en fait de zigzaguer d'un côté de l'identifiant à l'autre.
Maintenant déchiffrer une simple déclaration
int *a[10];
Application de la règle:
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`". ^^^
Décrypterons la déclaration complexe comme
void ( *(*f[]) () ) ();
en appliquant les règles ci-dessus:
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`" ^^^^
Voici un GIF montrant comment vous allez (cliquez sur l'image pour l'agrandir):
Les règles mentionnées ici sont tirées du livre C Programming A Modern Approach de KN KING .
la source
char (x())[5]
devrait entraîner une erreur de syntaxe mais, cdecl l'analyser comme: declarex
comme fonction retournant le tableau 5 dechar
.Ce n'est qu'une "spirale" car il se trouve qu'il n'y a, dans cette déclaration, qu'un seul opérateur de chaque côté dans chaque niveau de parenthèses. Prétendre que vous procédez "en spirale" vous suggérerait généralement d'alterner entre les tableaux et les pointeurs dans la déclaration
int ***foo[][][]
alors qu'en réalité tous les niveaux du tableau viennent avant l'un des niveaux du pointeur.la source
Je doute que de telles constructions puissent avoir une quelconque utilité dans la vie réelle. Je les déteste même en tant que questions d'entrevue pour les développeurs réguliers (probablement OK pour les rédacteurs de compilateurs). typedefs doit être utilisé à la place.
la source
En tant que faits triviaux aléatoires, vous pourriez trouver amusant de savoir qu'il existe un mot en anglais pour décrire la lecture des déclarations C: Boustrophédoniquement , c'est-à-dire alterner de droite à gauche avec de gauche à droite.
Référence: Van der Linden, 1994 - Page 76
la source
En ce qui concerne l'utilité de cela, lorsque vous travaillez avec le shellcode, vous voyez beaucoup cette construction:
int (*ret)() = (int(*)())code; ret();
Bien qu'il ne soit pas aussi compliqué d'un point de vue syntaxique, ce modèle particulier revient souvent.
Exemple plus complet dans cette question SO.
Donc, bien que l'utilité dans la mesure dans l'image originale soit discutable (je suggérerais que tout code de production devrait être considérablement simplifié), il y a quelques constructions syntaxiques qui apparaissent un peu.
la source
La déclaration
void (*(*f[])())()
est juste une façon obscure de dire
avec
typedef void (*ResultFunction)(); typedef ResultFunction (*Function)();
En pratique, des noms plus descriptifs seront nécessaires au lieu de ResultFunction et Function . Si possible, je spécifierais également les listes de paramètres comme
void
.la source
J'ai trouvé la méthode décrite par Bruce Eckel utile et facile à suivre:
Tiré de: Thinking in C ++ Volume 1, deuxième édition, chapitre 3, section "Function Addresses" de Bruce Eckel.
la source
Sauf comme modifié par des parenthèses, bien sûr. Et notez que la syntaxe pour les déclarer reflète exactement la syntaxe d'utilisation de cette variable pour obtenir une instance de la classe de base.
Sérieusement, ce n'est pas difficile à apprendre en un coup d'œil; vous devez simplement être prêt à passer du temps à pratiquer la compétence. Si vous souhaitez maintenir ou adapter du code C écrit par d'autres personnes, cela vaut vraiment la peine d'investir ce temps. C'est aussi une astuce de fête amusante pour effrayer les autres programmeurs qui ne l'ont pas appris.
Pour votre propre code: comme toujours, le fait que quelque chose puisse être écrit en une seule ligne ne signifie pas qu'il devrait l'être, à moins qu'il ne s'agisse d'un modèle extrêmement courant qui est devenu un idiome standard (comme la boucle de copie de chaîne) . Vous, et ceux qui vous suivent, serez beaucoup plus heureux si vous construisez des types complexes à partir de typedefs en couches et de déréférences étape par étape plutôt que de vous fier à votre capacité à les générer et à les analyser "en une seule fois". Les performances seront tout aussi bonnes, et la lisibilité et la maintenabilité du code seront considérablement meilleures.
Ça pourrait être pire, tu sais. Il y avait une déclaration PL / I légale qui commençait par quelque chose comme:
if if if = then then then = else else else = if then ...
la source
IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
et est analysée commeif (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
.Il se trouve que je suis l'auteur original de la règle de la spirale que j'ai écrite il y a tant d'années (quand j'avais beaucoup de cheveux :) et j'ai été honoré lorsqu'elle a été ajoutée au cfaq.
J'ai écrit la règle de la spirale pour permettre à mes étudiants et collègues de lire plus facilement les déclarations C "dans leur tête"; c'est-à-dire sans avoir à utiliser des outils logiciels comme cdecl.org, etc. Je n'ai jamais eu l'intention de déclarer que la règle de la spirale était la manière canonique d'analyser les expressions C. Je suis cependant ravi de voir que la règle a aidé littéralement des milliers d'étudiants et de praticiens en programmation C au fil des ans!
Pour mémoire,
Il a été "correctement" identifié à plusieurs reprises sur de nombreux sites, y compris par Linus Torvalds (quelqu'un que je respecte énormément), qu'il y a des situations où ma règle en spirale "s'effondre". Le plus courant étant:
char *ar[10][10];
Comme indiqué par d'autres dans ce fil, la règle pourrait être mise à jour pour indiquer que lorsque vous rencontrez des tableaux, consommez simplement tous les index comme s'ils étaient écrits comme:
char *(ar[10][10]);
Maintenant, en suivant la règle de la spirale, j'obtiendrais:
"ar est un tableau bidimensionnel 10x10 de pointeurs vers char"
J'espère que la règle en spirale continuera à être utile dans l'apprentissage de C!
PS:
J'adore l'image "C n'est pas difficile" :)
la source
(*(*f[]) ()) ()
Résolution
void
>>(*(*f[]) ())
() = videResoiving
()
>>(*f[]) ()
) = fonction retournant (void)Résolution
*
>>(*f[])
() = pointeur vers (fonction retournant (void))Résolution
()
>>f[]
) = fonction retournant (pointeur vers (fonction retournant (void)))Résolution
*
>>f
[] = pointeur vers (fonction retournant (pointeur vers (fonction retournant (void))))Résolution
[ ]
>>la source