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

189

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

du code c

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:

une explication intéressante

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.

Motun
la source
9
Comment l'explication pourrait-elle être plus simple? Il couvre tous les aspects de la définition de fw / quelques mots pour chaque point clé.
Scott Hunter
30
Peut-être que C est difficile? Cela se déclare en fait fcomme un tableau de pointeurs vers des fonctions qui pourraient prendre n'importe quel argument .. si c'était le cas void (*(*f[])(void))(void);, alors oui, ce seraient des fonctions qui ne prennent aucun argument ...
txtechhelp
18
En pratique, ne codez pas un code aussi obscur. Utilisez typedef pour les signatures
Basile Starynkevitch
4
toute déclaration impliquant des pointeurs de fonction peut être difficile. Cela ne veut pas dire que le C ou C ++ normal est dur de la même manière. D'autres langages résolvent cela de différentes manières, y compris ne pas avoir de pointeurs de fonction, ce qui peut être une omission importante dans certains cas
Kate Gregory
20
Si vous plissez les yeux, cela ressemble à LISP.
user2023861

Réponses:

117

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 :

Il y a trois étapes simples à suivre:

  1. En commençant par l'élément inconnu, déplacez-vous en spirale / sens horaire; lors de la rencontre des éléments suivants, remplacez-les par les instructions anglaises correspondantes:

    [X]ou []
    => Taille X du tableau de ... ou Taille du tableau non définie de ...

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

    *
    => pointeur (s) vers ...

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

  3. Résolvez toujours tout ce qui est entre parenthèses en premier!

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:

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

É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 que int **(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).

ouah
la source
5
Je souhaite que cdecl.org soit meilleur
Grady Player
8
Il n'y a pas de "règle en spirale" ... "int *** foo [] [] []" définit un tableau de tableaux de tableaux de pointeurs vers des pointeurs vers des pointeurs. La "spirale" vient seulement du fait que cette déclaration est arrivée à regrouper les choses entre parenthèses de manière à les faire alterner. Tout est à droite, puis à gauche, dans chaque ensemble de parenthèses.
Random832
1
@ Random832 Il existe une "règle en spirale", et elle couvre le cas que vous venez de mentionner, c'est-à-dire comment traiter les parenthèses / tableaux, etc. avec des déclarations compliquées. À mon humble avis , il est extrêmement utile et vous évite en cas de problème ou lorsque cdecl.org ne peut pas analyser la déclaration. Bien sûr, il ne faut pas abuser de telles déclarations, mais il est bon de savoir comment elles sont analysées.
vsoftco
5
@vsoftco Mais ce n'est pas "se déplacer en spirale / sens horaire" si vous ne vous retournez que lorsque vous atteignez les parenthèses.
Random832
2
ouah, vous devriez mentionner que la règle en spirale n'est pas universelle .
haccks le
106

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 aet il fpeut s'agir d'expressions plus compliquées que de simples identifiants; in T (*a)[N], apourrait ê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 que void (*(*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 signalfonction 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 fdans 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 utiliser f dans une expression (à savoir, (*(*f[i])())();en supposant qu'aucune des fonctions n'accepte d'arguments).

John Bode
la source
7
Merci d'avoir donné l'exemple de «signal», montrant que ce genre de choses apparaît dans la nature.
Justsalt
C'est un excellent exemple.
Casey
J'ai aimé votre farbre 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 :)
txtechhelp
1
en supposant qu'aucune des fonctions n'accepte d'arguments : alors vous devez utiliser voidentre parenthèses les fonctions, sinon elle peut prendre n'importe quel argument.
haccks le
1
@haccks: pour la déclaration, oui; Je parlais de l'appel de fonction.
John Bode le
58

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 type void. Ce qui signifie:

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

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

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

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

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

    (*(*f[i])())()
    
  • 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.

Jon Purdy
la source
3
Excellente façon de voir les choses que je n'ai jamais vue auparavant. +1
tbodt
4
Agréable. Vu de cette façon, c'est vraiment simple . En fait, plutôt plus facile que quelque chose du genre vector< function<function<void()>()>* > f, surtout si vous ajoutez le std::s. (Mais bon, l'exemple est artificiel ... a même l' f :: [IORef (IO (IO ()))]air bizarre.)
gauche vers le
1
@TimoDenk: La déclaration a[x]indique que l'expression a[i]est valide quand i >= 0 && i < x. Alors que, a[]laisse la taille non spécifiée, et est donc identique à *a: cela indique que l'expression a[i](ou de manière équivalente *(a + i)) est valide pour une plage de i.
Jon Purdy
4
C'est de loin le moyen le plus simple de penser aux types C, merci pour cela
Alex Ozer
4
J'aime cela! Beaucoup plus facile à raisonner que des spirales idiotes. (*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.
Lynn
32

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

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):

entrez la description de l'image ici


Les règles mentionnées ici sont tirées du livre C Programming A Modern Approach de KN KING .

piratages
la source
C'est exactement comme l'approche standard, c'est-à-dire "la déclaration reflète l'utilisation". J'aimerais cependant poser une autre question à ce stade: suggérez-vous le livre de KN King? Je vois beaucoup de bonnes critiques sur le livre.
Motun
1
Ouais. Je suggère ce livre. J'ai commencé à programmer à partir de ce livre. Bons textes et problèmes là-dedans.
haccks le
Pouvez-vous donner un exemple de cdecl qui ne comprend pas une déclaration? Je pensais que cdecl utilisait les mêmes règles d'analyse que les compilateurs, et pour autant que je sache, cela fonctionne toujours.
Fabio dit réintégrer Monica le
@FabioTurati; Une fonction ne peut pas renvoyer de tableaux ou de fonction. char (x())[5]devrait entraîner une erreur de syntaxe mais, cdecl l'analyser comme: declare xcomme fonction retournant le tableau 5 dechar .
haccks le
12

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.

Aléatoire832
la source
Eh bien, dans "l'approche en spirale", vous allez aussi loin que possible à droite, puis aussi loin à gauche que vous le pouvez, etc. Mais c'est souvent expliqué à tort ...
Lynn
7

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.

SergeyA
la source
3
Néanmoins, il est important de savoir comment l'analyser, ne serait-ce que pour savoir comment analyser le typedef!
inetknght
1
@inetknght, la façon dont vous le faites avec les typedefs est de les avoir assez simples pour qu'aucune analyse ne soit nécessaire.
SergeyA
2
Les personnes qui posent ce type de questions lors des entretiens ne le font que pour caresser leur Egos.
Casey
1
@JohnBode, et vous vous feriez une faveur en tapant la valeur de retour de la fonction.
SergeyA
1
@JohnBode, je trouve que c'est une question de choix personnel qui ne vaut pas la peine d'être débattue. Je vois votre préférence, j'ai toujours la mienne.
SergeyA
7

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

asamarin
la source
1
Ce mot n'indique pas dedans comme imbriqué par des parenthèses ou sur une seule ligne. Il décrit un motif "serpent", avec une ligne LTR suivie d'une ligne RTL.
Potatoswatter
5

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.

Casey
la source
5

La déclaration

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

est juste une façon obscure de dire

Function f[]

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.

August Karlstrom
la source
4

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 ni argument ni valeur de retour, vous dites:

void (*funcPtr)();

Lorsque vous regardez une définition complexe comme celle-ci, la meilleure façon de l'attaquer est de commencer au milieu et de vous en sortir. «Commencer au milieu» signifie commencer par le nom de la variable, qui est funcPtr. "Travailler votre chemin" signifie regarder vers la droite pour l'élément le plus proche (rien dans ce cas; la parenthèse droite vous arrête court), puis regarder vers la gauche (un pointeur indiqué par l'astérisque), puis regarder vers la droite (un liste d'arguments vide indiquant une fonction qui ne prend aucun argument), puis en regardant vers la gauche (void, qui indique que la fonction n'a pas de valeur de retour). Ce mouvement droite-gauche-droite fonctionne avec la plupart des déclarations.

Pour revoir, "commencez au milieu" ("funcPtr est un ..."), allez à droite (rien là-bas - vous êtes arrêté par la parenthèse droite), allez à gauche et trouvez le "*" (" ... pointeur vers un ... ”), allez à droite et trouvez la liste d'arguments vide (“ ... fonction qui ne prend aucun argument ... ”), allez à gauche et trouvez le vide (“ funcPtr est un pointeur vers une fonction qui ne prend aucun argument et renvoie void »).

Vous vous demandez peut-être pourquoi * funcPtr nécessite des parenthèses. Si vous ne les utilisiez pas, le compilateur verrait:

void *funcPtr();

Vous déclareriez une fonction (qui renvoie un void *) plutôt que de définir une variable. Vous pouvez penser que le compilateur passe par le même processus que vous le faites lorsqu'il détermine ce qu'une déclaration ou une définition est censée être. Il a besoin de ces parenthèses pour «se heurter» pour qu'il retourne à gauche et trouve le '*', au lieu de continuer vers la droite et de trouver la liste d'arguments vide.

Déclarations et définitions compliquées

En passant, une fois que vous avez compris comment fonctionne la syntaxe de déclaration C et C ++, 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() {} ///:~ 

Parcourez chacun d'eux et utilisez le guide droite-gauche pour le comprendre. Le chiffre 1 dit "fp1 est un pointeur vers une fonction qui prend un argument entier et renvoie un pointeur vers un tableau de 10 pointeurs vides."

Le 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 qui prend un argument entier et renvoie un float."

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

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

Vous aurez rarement, voire jamais, besoin de déclarations et de définitions aussi compliquées que celles-ci. Cependant, si vous passez par l'exercice de les comprendre, vous ne serez même pas légèrement dérangé par ceux légèrement compliqués que vous pourriez rencontrer dans la vraie vie.

Tiré de: Thinking in C ++ Volume 1, deuxième édition, chapitre 3, section "Function Addresses" de Bruce Eckel.

utilisateur3496846
la source
4

Souvenez-vous de ces règles pour les déclarations C
et la priorité ne sera jamais mise en doute:
commencez par le suffixe, continuez avec le préfixe,
et lisez les deux ensembles de l'intérieur vers l'extérieur.
- moi, milieu des années 1980

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 ...
keshlam
la source
2
L'instruction PL / I a été IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFet est analysée comme if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF).
Cole Johnson
Je pense qu'il y avait une version qui a fait un pas de plus en utilisant une expression conditionnelle IF / THEN / ELSE (équivalente à C? :), qui a introduit le troisième ensemble dans le mélange ... mais cela fait quelques décennies et peut-être dépendait d'un dialecte particulier de la langue. Il reste que toute langue a au moins une forme pathologique.
keshlam
4

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" :)

David Anderson
la source
3
  • néant (*(*f[]) ()) ()

Résolution void>>

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

Resoiving ()>>

  • (* (*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 [ ]>>

  • f = tableau de (pointeur vers (fonction retournant (pointeur vers (fonction retournant (void)))))
Shubham
la source