Pourquoi les définitions de pointeurs de fonction fonctionnent-elles avec un certain nombre d'esperluettes '&' ou d'astérisques '*'?

216

Pourquoi les travaux suivants fonctionnent-ils?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}
Jimmy
la source

Réponses:

224

Il y a quelques pièces à cela qui permettent à toutes ces combinaisons d'opérateurs de fonctionner de la même manière.

La raison fondamentale pour laquelle tous ces travaux sont qu'une fonction (comme foo) est implicitement convertible en un pointeur vers la fonction. C'est pourquoi void (*p1_foo)() = foo;works: fooest implicitement converti en un pointeur sur lui-même et ce pointeur est affecté à p1_foo.

L'unaire &, lorsqu'il est appliqué à une fonction, renvoie un pointeur vers la fonction, tout comme il donne l'adresse d'un objet lorsqu'il est appliqué à un objet. Pour les pointeurs vers des fonctions ordinaires, il est toujours redondant en raison de la conversion implicite de pointeur de fonction en fonction. En tout cas, c'est pourquoi ça void (*p3_foo)() = &foo;marche.

L'unaire *, lorsqu'il est appliqué à un pointeur de fonction, produit la fonction pointée, tout comme il renvoie l'objet pointé lorsqu'il est appliqué à un pointeur ordinaire sur un objet.

Ces règles peuvent être combinées. Considérez votre avant-dernier exemple **foo:

  • Tout d'abord, fooest implicitement converti en un pointeur sur lui-même et le premier *est appliqué à ce pointeur de fonction, ce qui donne à foonouveau la fonction .
  • Ensuite, le résultat est à nouveau implicitement converti en un pointeur sur lui-même et le second *est appliqué, ce qui donne à nouveau la fonction foo.
  • Il est ensuite implicitement converti à nouveau en un pointeur de fonction et affecté à la variable.

Vous pouvez ajouter autant de *s que vous le souhaitez, le résultat est toujours le même. Le plus *s, le plus joyeux.

Nous pouvons également considérer votre cinquième exemple &*foo:

  • Premièrement, fooest implicitement converti en un pointeur sur lui-même; l'unaire *est appliqué, cédant à foonouveau.
  • Ensuite, le &est appliqué à foo, ce qui donne un pointeur vers foo, qui est affecté à la variable.

La &seule peut être appliqué à une fonction bien, et non à une fonction qui a été converti en un pointeur de fonction ( à moins, bien sûr, le pointeur de fonction est une variable, auquel cas le résultat est un pointeur à un pointeur- à une fonction; par exemple, vous pouvez ajouter à votre liste void (**pp_foo)() = &p7_foo;).

C'est pourquoi &&foone fonctionne pas: &foon'est pas une fonction; c'est un pointeur de fonction qui est une valeur r. Cependant, &*&*&*&*&*&*foocela fonctionnerait, tout comme &******&foo, car dans ces deux expressions, le &est toujours appliqué à une fonction et non à un pointeur de fonction rvalue.

Notez également que vous n'avez pas besoin d'utiliser l'unaire *pour effectuer l'appel via le pointeur de fonction; les deux (*p1_foo)();et (p1_foo)();ont le même résultat, encore une fois à cause de la conversion de pointeur de fonction en fonction.

James McNellis
la source
2
@Jimmy: Ce ne sont pas des références à des pointeurs de fonction, ce ne sont que des pointeurs de fonction. &fooprend l'adresse de foo, ce qui se traduit par un pointeur de fonction pointant vers foo, comme on pourrait s'y attendre.
Dennis Zickefoose
2
Vous ne pouvez pas non plus chaîner des &opérateurs pour des objets: donnés int p;, &pdonne un pointeur vers pet est une expression rvalue; l' &opérateur requiert une expression lvalue.
James McNellis
12
Je ne suis pas d'accord. Le plus *, le moins joyeux .
Seth Carnegie
28
Veuillez ne pas modifier la syntaxe de mes exemples. J'ai choisi les exemples très spécifiquement pour démontrer les caractéristiques du langage.
James McNellis
7
En remarque, la norme C stipule explicitement qu'une combinaison d' &*annulations mutuelles (6.5.3.2): "The unary & operator yields the address of its operand."/ - / "If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.".
Lundin
9

Je pense qu'il est également utile de se rappeler que C n'est qu'une abstraction pour la machine sous-jacente et c'est l'un des endroits où cette abstraction fuit.

Du point de vue de l'ordinateur, une fonction n'est qu'une adresse mémoire qui, si elle est exécutée, exécute d'autres instructions. Ainsi, une fonction en C est elle-même modélisée comme une adresse, ce qui conduit probablement à la conception qu'une fonction est "la même" que l'adresse vers laquelle elle pointe.

madumlao
la source
0

&et *sont des opérations idempotentes sur un symbole déclaré en fonction de C qui signifie func == *func == &func == *&funcet donc*func == **func

Cela signifie que le type int ()est identique à int (*)()un paramètre de fonction et qu'une fonction définie peut être passée en tant que *func, funcou &func. (&func)()est le même que func(). Lien Godbolt.

Une fonction est donc vraiment une adresse, *et &n'a donc aucune signification, et au lieu de produire une erreur, le compilateur choisit de l'interpréter comme l'adresse de func.

&sur un symbole déclaré en tant que pointeur de fonction obtiendra cependant l'adresse du pointeur (car il a maintenant un objectif distinct), tandis que funcpet *funcpsera identique

Lewis Kelsey
la source