202 votes

void ( *( *f[] ) () ) ()

Je viens de voir une photo aujourd'hui et je pense que j'apprécierais des explications. Voici donc l'image :

some c code

J'ai trouvé cela déroutant et je me suis demandé si de tels codes sont jamais pratiques. J'ai cherché l'image sur Google et j'ai trouvé une autre image dans ce reddit, et voici cette photo :

some interesting explanation

Donc cette "lecture en spirale" est quelque chose de valide ? Est-ce ainsi que les compilateurs C analysent les données ?
Ce serait génial s'il y avait des explications plus simples pour ce code bizarre.
En dehors de tout cela, ce type de codes peut-il être utile ? Si oui, où et quand ?

Il y a une question à propos de la "règle de la spirale", mais je ne demande pas seulement comment elle est appliquée ou comment les expressions sont lues avec cette règle. Je m'interroge sur l'utilisation de ces expressions et sur la validité de la règle de la spirale. A ce sujet, quelques réponses intéressantes ont déjà été postées.

120voto

ouah Points 75311

Il existe une règle appelée "Clockwise/Spiral Rule" pour aider à trouver la signification d'une déclaration complexe.

De c-faq :

Il y a trois étapes simples à suivre :

  1. En commençant par l'élément inconnu, avancez dans le sens de la spirale/des aiguilles d'une montre ; lorsque vous rencontrez les éléments suivants, remplacez-les par les énoncés anglais correspondants :

    [X] o []
    \=> Tableau de taille X de... ou Tableau de taille indéfinie de...

    (type1, type2)
    \=> fonction passant par type1 et type2 retournant...

    *
    \=> pointeur(s) vers...

  2. Continuez à faire cela en spirale dans le sens des aiguilles d'une montre jusqu'à ce que tous les jetons aient été couverts.

  3. Résolvez toujours d'abord tout ce qui est entre parenthèses !

Vous pouvez consulter le lien ci-dessus pour des exemples.

Notez également que pour vous aider, il existe un site web appelé :

http://www.cdecl.org

Vous pouvez entrer une déclaration C et il donnera sa signification anglaise. Pour

void (*(*f[])())()

qu'il produit :

déclarer f comme un tableau de pointeur vers la fonction retournant le pointeur vers la fonction retournant void

EDITAR:

Comme le soulignent 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 pour int **x[1][2]; la règle de la spirale ignore le fait que [] a une plus grande priorité que * .

Devant un tableau de tableaux, on peut d'abord ajouter des parenthèses explicites avant d'appliquer la règle de la spirale. Par exemple : int **x[1][2]; est la même chose que int **(x[1][2]); (également valable en C) en raison de la préséance et de la règle de la spirale, il est alors correctement lu comme "x est un tableau 1 de tableau 2 de pointeur à pointeur à int", ce qui est la déclaration anglaise correcte.

Notez que cette question a également été traitée dans le présent document. réponse por James Kanze (souligné par haccks dans les commentaires).

117voto

John Bode Points 33046

La règle de la "spirale" est en quelque sorte la conséquence des règles de préséance 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

L'indice [] et l'appel de fonction () ont une priorité plus élevée que les opérateurs unaires * donc *f() est interprété comme *(f()) et *a[] est interprété comme *(a[]) .

Ainsi, si vous voulez un pointeur vers un tableau ou un pointeur vers une fonction, vous devez explicitement regrouper les éléments suivants * avec l'identifiant, comme dans (*a)[] o (*f)() .

Puis vous réalisez que a et f peuvent être des expressions plus compliquées que de simples identificateurs ; dans le cas de T (*a)[N] , a peut ê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 encore un tableau de pointeurs vers des fonctions telles que (*(*p[M])())[N] ( a -> (*p[M])() ), etc.

Il serait bien que l'opérateur d'indirection * était postfixe au lieu d'unaire, ce qui rendrait les déclarations un peu plus faciles à lire de gauche à droite ( void f[]*()*(); est définitivement plus fluide que void (*(*f[])())() ), mais ce n'est pas le cas.

Quand vous tombez sur une déclaration poilue comme celle-là, commencez par trouver la le plus à gauche et appliquer les règles de précédence ci-dessus, en les appliquant récursivement à tous les paramètres de la 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

El signal 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 voulez-vous utiliser f dans une expression ? Vous savez que c'est un tableau de pointeurs, mais comment l'utiliser pour exécuter la bonne fonction ? Vous devez passer en revue les typedefs et trouver la syntaxe correcte. En revanche, la version "nue" est assez simple, mais elle vous dit exactement comment faire pour utiliser f dans une expression (à savoir, (*(*f[i])())(); (en supposant qu'aucune des deux fonctions ne prend d'arguments).

64voto

Jon Purdy Points 19408

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 affirmation que l'expression (*(*f[i])())() produit un résultat de type void . Ce qui veut dire :

  • f doit être un tableau, puisque vous pouvez l'indexer :

    f[i]
  • Les éléments de f doivent être des pointeurs, puisque vous pouvez les déréférencer :

    *f[i]
  • Ces pointeurs doivent être des pointeurs vers des fonctions ne prenant pas d'arguments, puisque vous pouvez les appeler :

    (*f[i])()
  • Les résultats de ces fonctions doivent également être des pointeurs, puisque vous pouvez les déréférencer :

    *(*f[i])()
  • Ces pointeurs doivent également sont des pointeurs vers des fonctions ne prenant pas d'arguments, puisque vous pouvez les appeler :

    (*(*f[i])())()
  • Ces pointeurs de fonction doivent retourner void

La "règle de la spirale" n'est qu'un moyen mnémotechnique qui permet de comprendre différemment une même chose.

42voto

haccks Points 33022

Donc cette "lecture en spirale" est quelque chose de valable ?

Appliquer la règle de la spirale ou utiliser cdecl ne sont pas toujours valables. Les deux échouent dans certains cas. La règle de la spirale fonctionne dans de nombreux cas, mais elle n'est pas universelle .

Pour décrypter des déclarations complexes, rappelez-vous ces deux règles simples :

  • Toujours lire les déclarations de l'intérieur vers l'extérieur : Commencez à partir de la parenthèse la plus intérieure, s'il y en a une. Localisez l'identifiant qui est déclaré, et commencez à déchiffrer la déclaration à partir de là.

  • *Lorsqu'il y a un choix à faire, privilégiez toujours [] et () sur `** : Siprécède l'identifiant et[]le suit, l'identifiant représente un tableau, et non un pointeur. De même, siprécède l'identifiant et()le suit, l'identifiant représente une fonction, et non un pointeur. (Il est toujours possible d'utiliser des parenthèses pour remplacer la priorité normale de la fonction[]et()sur*` .)

Cette règle implique en fait zigzaguant d'un côté de l'identifiant à l'autre.

Maintenant, décryptage d'une simple déclaration

int *a[10];

Appliquer 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écryptons la déclaration complexe suivante

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 qui montre comment procéder (cliquez sur l'image pour l'agrandir) :

enter image description here


Les règles mentionnées ici sont tirées du livre <a href="http://rads.stackoverflow.com/amzn/click/0393979504" rel="nofollow">C Programming A Modern Approach par K.N KING </a>.

12voto

Random832 Points 9199

Ce n'est une "spirale" que parce qu'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. Affirmer que vous procédez "en spirale" suggère généralement que vous alternez entre tableaux et pointeurs dans la déclaration. int ***foo[][][] alors qu'en réalité tous les niveaux du tableau viennent avant tous les niveaux du pointeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X