C n'est pas si difficile que ça: void (*(*f[]) ()) ()

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

some c code

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 ce entrée reddit, et voici cette image:

some interesting explanation

donc cette "lecture en spirale" est quelque chose de Valable? C'est comme ça que les compilateurs C se débrouillent?

Ce serait bien s'il y avait des explications plus simples pour ce code bizarre.

En dehors de tout, ce genre de codes peut-il être utile? Si oui, où et quand?

Il y a une question à propos de "spirale de la règle", mais je ne suis pas juste de demander à propos de la façon dont il est appliqué ou comment les expressions sont lus à cette règle. Je suis remise en question de l'usage de ces expressions et de la validité de la règle spirale. A ce propos, quelques bonnes réponses sont déjà postées.

179
demandé sur Motun 2015-12-31 19:01:28
la source

13 ответов

il y a une règle appelée " Clockwise/Spiral Rule " pour aider à trouver le sens d'une déclaration complexe.

à Partir de c-faq :

il y a trois étapes simples à suivre:

  1. en commençant par l'élément inconnu, se déplacer en spirale/dans le sens des aiguilles d'une montre; lorsque les éléments suivants sont arrondis, les remplacer par les éléments suivants: déclarations correspondantes en anglais:

    [X] ou []

    => Array X taille des... ou Matrice de taille non définie...

    (type1, type2)

    = > fonction passant le retour de type1 et type2...

    *

    => pointeur(s)...

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

  3. toujours résoudre quoi que ce soit entre parenthèses d'abord!

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

aussi noter que pour vous aider Il ya aussi un site Web appelé:

http://www.cdecl.org

vous pouvez entrer une déclaration C et il donnera sa signification (en anglais). Au lieu de

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

c'sorties:

déclarer f en tant que tableau de pointeur de fonction retournant le pointeur de fonction retournant void

EDIT:

comme indiqué dans les commentaires de Random832 , la règle de spirale ne traite pas le tableau des tableaux et conduira à un résultat erroné dans (la plupart des) ces déclarations. Par exemple, pour int **x[1][2]; , la règle de la spirale ne tient pas compte du fait que [] a priorité sur * .

quand devant le 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 que int **(x[1][2]); (également valide C) en raison de la préséance et la règle en spirale alors correctement lit comme "x est un tableau 1 du tableau 2 de pointeur à pointeur à int" qui est la correcte l'anglais de la déclaration.

il est à noter que cette question a aussi été abordée dans cette réponse par James Kanze (souligné par haccks dans les commentaires).

112
répondu ouah 2017-05-23 13:30:45
la source

la règle "en spirale" tombe en quelque sorte 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 () les opérateurs ont une priorité plus élevée que * , de sorte que *f() est interprété comme *(f()) et *a[] est interprété comme *(a[]) .

donc si vous voulez un pointeur vers un tableau ou un pointeur vers une fonction, alors vous devez explicitement grouper le * avec le identificateur, comme (*a)[] ou (*f)() .

alors vous réalisez que a et f peuvent être des expressions plus compliquées que de simples identificateurs; dans T (*a)[N] , a pourrait être un simple identifiant, ou il pourrait être un appel de fonction comme (*f())[N] ( a -> f() ), ou ça pourrait être un tableau comme (*p[M])[N] , ( a -> p[M] ), ou il pourrait être un tableau de pointeurs à des fonctions comme (*(*p[M])())[N] ( a -> (*p[M])() ), etc.

il serait bien que l'opérateur indirect * soit postfix au lieu de unary, ce qui rendrait les déclarations un peu plus faciles à lire de gauche à droite ( void f[]*()*(); coule certainement mieux que void (*(*f[])())() ), mais ce n'est pas le cas.

lorsque vous tombez sur une déclaration chevelue comme celle-ci, commencez par trouver l'identificateur le plus à gauche et appliquez les règles de priorité ci-dessus., en les appliquant de manière récursive à n'importe quel paramètre 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 fonction signal dans la bibliothèque standard est probablement le spécimen type pour ce genre de démence:

       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 typographies", ce qui est certainement une option:

typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];

mais...

comment utilisez f dans une expression? Vous savez c'est un tableau de pointeurs, mais comment voulez-vous utiliser pour exécuter la fonction correcte? Vous devez passer en revue les typographies et trouver la syntaxe correcte. En revanche, la version" nue "est assez eyestabby, mais il vous dit exactement comment utiliser f dans une expression (à savoir, (*(*f[i])())(); , en supposant qu'aucune des deux fonctions ne prend des arguments).

100
répondu John Bode 2015-12-31 21:30:29
la source

En C, la déclaration des miroirs d'utilisation-c'est la façon dont il est défini dans la norme. La déclaration:

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

est une affirmation selon laquelle l'expression (*(*f[i])())() produit un résultat du type void . Qui signifie:

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

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

    *f[i]
    
  • ces pointeurs doivent être des pointeurs vers des fonctions ne prenant aucun argument, puisque vous pouvez les appeler:

    (*f[i])()
    
  • les résultats de ces fonctions doivent aussi être des pointeurs, car vous pouvez les déréférer:

    *(*f[i])()
    
  • ces pointeurs doivent aussi être des pointeurs à des fonctions prenant no arguments, puisque vous pouvez les appeler:

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

la "règle en spirale" est juste un mnémonique qui fournit une manière différente de comprendre la même chose.

54
répondu Jon Purdy 2016-01-01 04:27:40
la source

donc cette "lecture en spirale" est quelque chose de Valable?

application de la règle de la spirale ou utilisation de cdecl ne sont pas toujours valables. Les deux échoue 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:

  • toujours lire les déclarations de l'intérieur : commencer par la parenthèse interne, 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, toujours privilégier [] et () plutôt que * : si * précède l'identificateur et [] le suit, l'identificateur 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 () par * .)

cette règle concerne en fait zigzagging d'un côté de l'identificateur à l'autre.

déchiffrer maintenant 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échiffrons 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 (cliquer sur l'image pour l'agrandir):

enter image description here


le règlement mentionné ici est tiré du livre C Programmation une approche moderne par K. N KING .

29
répondu haccks 2017-05-23 14:47:35
la source

c'est seulement une" spirale " parce qu'il se trouve qu'il n'y a, dans cette déclaration, qu'un seul opérateur de chaque côté à l'intérieur de chaque niveau de parenthèses. Prétendre que vous procédez "en spirale" en général vous suggérerait d'alterner entre les tableaux et les pointeurs dans la déclaration int ***foo[][][] alors qu'en réalité tous les niveaux de tableau viennent avant n'importe lequel des niveaux de pointeur.

12
répondu Random832 2016-01-01 04:46:47
la source

je doute que de telles constructions puissent avoir une quelconque utilité dans la vie réelle. Je les déteste même comme questions d'interview pour les développeurs réguliers (probablement OK pour les rédacteurs de compilateurs). typedefs devrait être utilisé à la place.

7
répondu SergeyA 2015-12-31 19:11:13
la source

comme un factoid aléatoire, vous pourriez trouver amusant de savoir qu'il ya un mot réel en anglais pour décrire la façon dont les déclarations C sont lues: Boustrophedoniquement , c'est-à-dire, alternant droite-à-gauche avec gauche-à-droite.

référence: Van der Linden, 1994 - Page 76

7
répondu asamarin 2016-01-01 23:59:37
la source

en ce qui concerne l'utilité de ceci, en travaillant avec shellcode vous voyez cette construction beaucoup:

int (*ret)() = (int(*)())code;
ret();

bien qu'il ne soit pas aussi compliqué sur le plan syntaxique, ce motif particulier revient souvent.

exemple plus complet dans cette ainsi question.

donc, alors que l'utilité dans la mesure où dans l'image originale est discutable (je suggérerais que n'importe quel code de production devrait être drastiquement simplifié), il y a quelques constructions syntaxiques qui viennent un peu.

5
répondu Casey 2015-12-31 19:59:24
la source

la déclaration

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

est juste une manière obscure de dire

Function f[]

avec

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

dans la pratique, des noms plus descriptifs seront nécessaires au lieu de ResultFunction et Function . Si possible, je spécifierais aussi les listes de paramètres comme void .

5
répondu August Karlstrom 2016-01-11 21:08:29
la source

J'ai trouvé la méthode décrite par Bruce Eckel utile et facile à suivre:

définition d'un pointeur de fonction

Pour définir un pointeur vers une fonction qui n'a pas d'arguments et pas de retour valeur, vous dites:

void (*funcPtr)();

quand vous regardez une définition complexe comme cela, la meilleure façon de l'attaquer est de commencer dans le milieu et de travail votre moyen de sortir. "commençant au milieu" signifie commençant à la variable nom, qui est funcPtr. "Trouver la sortie" signifie se tourner vers le droit la plus proche de l'élément (rien dans ce cas; le droit La Parenthèse vous arrête court), puis en regardant à gauche (un pointeur indiqué par l'astérisque), puis en regardant vers la droite (un argument vide liste indiquant une fonction qui ne prend pas d'arguments), puis regarder la gauche (vide, qui indique que la fonction n'a pas valeur de retour). Cette droite-gauche-droite de mouvement fonctionne avec la plupart des déclarations.

revoir, "du milieu" ("funcPtr est un ..."), allez vers la droite (rien – vous êtes arrêté par la parenthèse droite), aller à la gauche et de trouver le ‘*’ ("... pointeur vers un ..."), allez vers la droite et trouver la liste des arguments vides ("... fonction qui ne prend pas d'arguments ... "), aller à gauche et à trouver le vide ("funcPtr est un pointeur vers une fonction qui ne prend pas d'arguments et void").

vous pouvez vous demander pourquoi *funcPtr exige des parenthèses. Si vous n'utilisez pas eux, le compilateur verrait:

void *funcPtr();

vous déclareriez une fonction (qui renvoie une void*) plutôt que de définir une variable. Vous pouvez penser au compilateur comme passer par le même processus que vous faites quand il découvre ce que déclaration ou définition est censé être. Il a besoin de ces des parenthèses pour "heurtent" donc, il va vers la gauche et trouve le ‘*’, au lieu de continuer vers la droite et trouver le vide liste d'arguments.

Compliqué les déclarations et définitions

comme une mise à part, une fois que vous comprenez comment la syntaxe de la déclaration C et C++ fonctionne vous pouvez créer des éléments beaucoup plus compliqués. Par exemple:

//: 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() {} ///:~ 

marchez à travers chacun d'eux et utilisez la droite-gauche ligne directrice pour le comprendre. Numéro 1 dit "fp1 est un pointeur vers une fonction qui prend un argument entier et renvoie un pointeur à un tableau de 10 pointeurs de vide."

Numéro 2 dit "fp2 est un pointeur vers une fonction qui prend trois arguments (int, int et float), et renvoie un pointeur vers une fonction cela prend un argument entier et renvoie un float."

si vous créez beaucoup de compliqué définitions, vous pouvez pour utiliser un typedef. numéro 3 montre comment un typedef évite de taper description compliquée à chaque fois. Il est écrit: "un PC3 est un indicateur d'un fonction qui ne prend aucun argument et renvoie un pointeur vers un tableau de 10 pointeurs vers des fonctions qui ne prennent pas d'arguments et renvoient des doubles." Ensuite, il est dit " a est l'un de ces types de PC3."typedef est généralement utile pour construire des descriptions compliquées à partir de simples.

Numéro 4 est une déclaration de fonction au lieu d'une définition de variable. Il est dit " f4 est une fonction qui renvoie un pointeur à un tableau de 10 pointeurs vers les fonctions qui renvoient des entiers."

vous aurez rarement, voire jamais besoin de telles déclarations compliquées et les définitions de ces. Cependant, si vous allez par le biais de l'exercice de en les comprenant, vous ne serez même pas légèrement dérangé par le un peu compliquées que vous pouvez rencontrer dans la vraie vie.

tiré de: Pensée en C++, Volume 1, deuxième édition, chapitre 3, section "Adresses de Fonction" par Bruce Eckel.

4
répondu user3496846 2016-01-03 04:29:31
la source

rappelez-vous ces règles pour C déclare

Et la préséance ne sera jamais en doute:

Commencer par le suffixe, continuer avec le préfixe

Et lisez les deux séries de l'intérieur, dehors.

-- moi, milieu des années 1980

sauf entre parenthèses, bien sûr. Et notez que la syntaxe pour déclarer ces exactement miroirs la syntaxe pour utiliser cette variable pour obtenir une instance de la classe de base.

sérieusement, ce n'est pas difficile à apprendre à faire à un coup d'oeil; vous devez juste être prêt à passer un certain temps à pratiquer la compétence. Si vous allez maintenir ou adapter le code C écrit par d'autres personnes, c'est certainement qui vaut la peine d'investir ce temps. C'est aussi un truc de fête amusant pour effrayer d'autres programmeurs qui ne l'ont pas appris.

Pour votre propre code: comme toujours, le fait que quelque chose puisse être écrit comme une doublure unique ne signifie pas qu'il devrait l'être, à moins que ce soit un motif extrêmement commun qui est devenu un idiome standard (tel que la boucle chaîne-copie). Vous, et ceux qui vous suivent, sera beaucoup plus heureux si vous construisez des types complexes à partir de typographies stratifiées et déréférences étape par étape plutôt que de compter sur votre capacité à générer et à analyser ces "à une bonne foop."La Performance sera tout aussi bon, et le code la lisibilité et la maintenabilité sera énormément mieux.

ça pourrait être pire, Tu sais. Il y avait une déclaration PL/I légale qui a commencé avec quelque chose comme:

if if if = then then then = else else else = if then ...
4
répondu keshlam 2016-01-03 12:17:08
la source
  • nul (*(*f[]) ()) ()

Résolution void >>

  • (*(*f[]) ()) () = nul

Resoving () > >

  • (* (*f[]) () ) = fonction returning (void)

Résolution * >>

  • (*f[]) () = pointeur vers (fonction de retour (void) )

Résolution () >>

  • (* f[] ) = la fonction de retour (pointeur (fonction de retour (void) ))

Résolution * >>

  • f [] = pointeur vers (retour de la fonction) pointeur vers (retour de la fonction) (nul) )))

Résolution [ ] >>

  • f = array (pointeur (fonction de retour (pointeur (fonction renvoi (nul)) ))))
2
répondu Shubham 2016-01-25 09:28:49
la source

il se trouve que je suis l'auteur original de la règle de la spirale que j'ai écrite oh il y a tant d'années (quand j'avais beaucoup de cheveux :) et j'ai été honoré quand elle a été ajoutée au cfaq.

j'ai écrit la règle en spirale comme un moyen de rendre plus facile pour mes étudiants et collègues de lire les déclarations C "dans leur tête"; c.-à-d., 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 soit la façon canonique de séparer les expressions. Je suis bien, heureux de voir que la règle a aidé des milliers de programmation en C des étudiants et des praticiens au fil des ans!

Pour le dossier

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"se brise". Les plus courants sont:

char *ar[10][10];

comme d'autres l'ont souligné dans la présente thread, la règle pourrait être mise à jour pour dire que lorsque vous rencontrez des tableaux, consommez simplement tous les index comme si écrit comme:

char *(ar[10][10]);

maintenant, suivant la règle de la spirale, j'obtiendrais:

"ar est un 10x10 tableau à deux dimensions de pointeurs sur char"

j'espère que la spirale de la règle porte sur son utilité dans l'apprentissage du C!

P.S.:

j'aime "C n'est pas "image :)

2
répondu David Anderson 2017-06-23 04:51:39
la source