34 votes

Quelle est la signification de ce morceau de code ? void (*signal(int sig, void (*func)(int)))(int) ;

Je suis tombé sur ce morceau de code et je me suis complètement perdu en interprétant sa signification.

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

Quelle est l'explication détaillée du code à la ligne 2 ?

Je sais que void y int sont des types, le *func est un pointeur pour une fonction, et les parenthèses sont pour la priorité. Mais je ne comprends toujours pas le (*signal ...), le (int), et tout le reste combiné ensemble. Plus c'est détaillé, mieux c'est.

J'ai probablement connu le sens/effet de cette déclaration. Mais j'ai dû faire quelques essais supplémentaires pour m'aider à comprendre ce qui se passe, comme ci-dessous :

  1 #include <signal.h>
  2 void (*signal)(int sig, void (*func)(int));
  3 void (*signal)(int);  // then void (signal)(int) again.
  4 //void (*signal(int sig, void (*func)(int)))(int); //break this line into two lines above
  5
  6 int main(){}

Dans le code ci-dessus, j'ai cassé void (*signal(int sig, void (*func)(int)))(int) en deux lignes. Pour la ligne 3, j'ai essayé les deux void (*signal)(int) y void (signal)(int) avec le même résultat d'erreur qui indiquait que j'essayais de redéclarer signal :

TestDeclaration.c:2 : error : 'signal' redeclared as different kind of symbol /usr/include/signal.h:93 : error : previous declaration of 'signal' was here
TestDeclaration.c:3 : error : 'signal' redeclared as different kind of symbol. /usr/include/signal.h:93 : error : previous declaration of 'signal' was here

Maintenant, je sais que les deux essais sont des manières incorrectes de déclarer, mais pourquoi sont-elles incorrectes ? Pourquoi la manière originale de déclarer n'est-elle PAS une redéclaration ?

40voto

Charles Bailey Points 244082

Il s'agit de la déclaration d'une fonction prenant un int et un pointeur vers une fonction (prenant int renvoyant void) et renvoyant un pointeur vers une fonction (prenant int et de retourner à la case départ).


Explication, ou guide d'interprétation

Vous pouvez interpréter en traitant tout ce qui est entre parenthèses comme une seule entité, puis en travaillant vers l'intérieur en utilisant la règle "la déclaration suit l'usage".

void (*signal(int sig, void (*func)(int))) (int) ;

L'entité entre parenthèses ressemble à une fonction prenant int et renvoyant void .

J'enlève la partie extérieure :

*signal(int sig, void (*func)(int))

Donc, signal prend des paramètres et renvoie quelque chose qui peut être déréférencé (en raison de la présence de l'en-tête * ) pour former une fonction prenant int et renvoyant void .

Cela signifie signal est une fonction qui renvoie un pointeur vers une fonction (en prenant la fonction int et renvoyant void ).

En regardant les paramètres, il prend un int (c'est-à-dire sig ) et void (*func)(int) qui est un pointeur vers une fonction (prenant le nom de int et renvoyant void ).

8voto

Il s'agit là d'un des exemples classiques de la complexité des déclarations C.
Pour comprendre cette déclaration, il est généralement utile d'introduire un typedef :

typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);

Le typedef déclare un pointeur vers une fonction (prenant un paramètre int et ne retournant rien). La fonction signal peut maintenant être considérée comme une fonction qui prend deux paramètres (un int et un pointeur vers une fonction) et renvoie un pointeur vers une fonction.

Cela peut également être obtenu à partir de la déclaration originale, mais cela demande un peu de pratique. La méthode habituelle consiste à partir de l'identifiant qui nomme l'entité la plus extérieure ( signal dans ce cas) :

signal est un ...

Ensuite, vous lisez à droite jusqu'à ce que vous trouviez une parenthèse fermante non appariée ou la fin de la déclaration : void (*_signal_~~(int sig, void (*func)(int)~~)(int)

signal est une fonction qui prend ... et retourne ...

Maintenant vous pouvez choisir entre analyser les paramètres en premier, ou la valeur de retour en premier. Je vais commencer par la valeur de retour. Pour cela, il faut lire à l'envers pour trouver la parenthèse ouverte correspondante : void ~~(~~__signal( /_ ... */ )_~~)~~(int)

`signal est une fonction qui prend ... et retourne un pointeur vers ...

En lisant dans les deux sens de cette façon, on arrive à des étapes successives :

`signal est une fonction prenant ... retournant un pointeur vers un (fonction prenant ... retournant ...)

`signal est une fonction prenant ... retournant un pointeur vers un (fonction prenant ... retournant void)

`signal est une fonction prenant ... retournant un pointeur vers un (fonction prenant un int et retournant void)

`signal est une fonction prenant deux paramètres : (un int) et (un pointeur vers une fonction prenant un int et retournant void), et retournant un pointeur vers une (fonction prenant un int et retournant void)

2voto

draganicimw Points 11

Prenons un exemple de l'utilisation de cette méchante déclaration :

void (*signal(int sig, void (*func)(int)))(int);

Sans trop de verbiage, on pourrait dire que "signal" est une fonction avec deux paramètres qui renvoie une fonction.

#include <stdio.h>

// First function that could be returned by our principal function
// Note that we can point to it using void (*f)(int)
void car_is_red(int color)
{
    printf("[car_is_red] Color %d (red) is my favorite color too !\n", color);
}

// Second function that could be returned by our principal function
// Note that we can point to it using void (*f)(int)
void car_is_gray(int color)
{
    printf("[car_is_gray] I don't like the color %d (gray) either !\n", color);
}

// The function taken as second parameter by our principal function
// Note that we can point to it using void (*func)(int)
void show_car_type(int mod)
{
    printf("[show_car_type] Our car has the type: %d\n",mod);
}

/* Our principal function. Takes two parameters, returns a function. */
void (* show_car_attributes(int color, void (*func)(int)) )(int)
{
    printf("[show_car_attributes] Our car has the color: %d\n",color); // Use the first parameter

    int mod = 11;  // Some local variable of our function show_car_attributes()
    func(mod);  // Call the function pointed by the second parameter (i.e. show_car_type() )

    // Depending on color value, return the pointer to one of two functions
    // Note that we do NOT use braces with function names
    if (color == 1)
        return car_is_red;
    else
        return car_is_gray;
    }

//main() function
int main()
{
    int color = 2;   // Declare our color for the car
    void (*f)(int);  // Declare a pointer to a function with one parameter (int)

    f = show_car_attributes(color, show_car_type); // f will take the return 
           // value of our principal function. Stated without braces, the 
           // parameter  "show_car_types" is a function pointer of type 
           // void (*func)(int).

    f(color);  // Call function that was returned by show_car_attributes()

    return 0;
}

Voyons ce qui va sortir :

Si la couleur = 1

[show_car_attributes] Our car has the color: 1
[show_car_type] Our car has the type: 11
[car_is_red] Color 1 (red) is my favorite color too !

Si la couleur = 2

[show_car_attributes] Our car has the color: 2
[show_car_type] Our car has the type: 11
[car_is_gray] I don't like the color 2 (gray) either !

2voto

keshlam Points 5979

Il s'agit d'un moyen mnémotechnique que j'ai créé il y a de nombreuses années et qui est très utile pour comprendre des types complexes :

Remember these rules for C declares
And precedence never will be in doubt
Start with the Suffix, Proceed with the Prefix
And read both sets from the inside, out.

Sauf lorsque les parenthèses modifient cette préséance, bien sûr.

En l'appliquant à ce cas :

void (*signal(int sig, void (*func)(int)))(int);

signal is:
  [inside parentheses]
  [suffix ()] a function, whose arguments are
    sig, which is [prefix int] an integer, and
      func, which is:
         [inside parentheses]
           [no suffix within these parens]
           [prefix *] a pointer to
         [suffix ()] a function, whose argument is
           an int
         [no more suffixes]
         [prefix void] and which returns void
         [no more prefixes]
       [no more arguments]
     [prefix *] And which returns a pointer to
     [no more prefixes within these parens]
   [suffix ()] a function, whose argument is
      an int
   [no more suffixes]
   [prefix void] and which returns void.

Avec un peu de pratique, vous arriverez à faire tout cela à la volée :

"Signal is function, whose arguments are:
    sig, an integer,
    and func, a pointer to a function whose argument is an int and which returns void
... which returns a pointer to a function that takes int as an argument and returns void.

(Désolé pour l'erreur de la première fois - je manque de pratique).

Oui, ce moyen mnémotechnique (avec le sous-entendu "à l'exception des parenthèses, bien sûr") fonctionne pour toutes les déclarations C, même si les pointeurs, les tableaux et les fonctions sont mal mélangés.

C'est une compétence VRAIMENT utile pour comprendre le fonctionnement du code de quelqu'un d'autre... ou même pour comprendre quelque chose de votre propre code que vous n'avez pas vu depuis longtemps.

Mais, oui, la meilleure façon de gérer tout ce que vous ne pensez pas que les gens seront capables de lire en un coup d'œil est de le construire en couches avec des typedefs. Les types de composants sont susceptibles d'être utiles eux-mêmes, et le fait de procéder étape par étape évite aux gens de se perdre en essayant de comprendre quelle parenthèse correspond à quoi. Soyez gentil avec la prochaine personne qui touchera votre code !

Si vous trouvez ce moyen mnémotechnique utile, n'hésitez pas à le citer ailleurs - mais attribuez-moi le mérite de son auteur, s'il vous plaît.

À propos, il existe aussi des outils "C Explainer" qui analysent les delaractions C et font la conversion en description anglaise pour vous. Le mien s'appelle CEX, pour des raisons évidentes, mais il en existe beaucoup d'autres et vous devriez être capable d'en trouver un si vous ne voulez pas engager cette compétence dans le wetware ou si quelqu'un vous donne quelque chose qui est vraiment trop laid pour que vous puissiez en garder la trace.

0voto

Vijay Points 17992

Pointeur de retour vers une fonction qui prend un :

  • entier comme premier argument et
  • un pointeur vers une fonction (qui prend un int et retourne void) comme argument en tant que second argument.

Et prend un argument entier.

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