web-dev-qa-db-fra.com

Comment le déréférencement d'un pointeur de fonction se produit-il?

Pourquoi et comment le déréférencement d'un pointeur de fonction "ne fait rien"?

Voilà de quoi je parle:

#include<stdio.h>

void hello() { printf("hello"); }

int main(void) { 
    (*****hello)(); 
}

D'un commentaire sur ici :

les pointeurs de fonction déréférencent très bien, mais l'indicateur de fonction résultant sera immédiatement reconverti en un pointeur de fonction


Et à partir d'une réponse ici :

Déréférencer (comme vous le pensez) le pointeur d'une fonction signifie: accéder à une mémoire CODE comme ce serait une mémoire DATA.

Le pointeur de fonction n'est pas supposé être déréférencé de cette façon. Au lieu de cela, il est appelé.

J'utiliserais un nom "déréférencement" côte à côte avec "appel". C'est bon.

Quoi qu'il en soit: C est conçu de telle manière que l'identifiant du nom de la fonction ainsi que le pointeur de la fonction de maintien de variable signifient la même chose: l'adresse de la mémoire CODE. Et cela permet de passer à cette mémoire en utilisant la syntaxe call () soit sur un identifiant soit sur une variable.


Comment exactement le déréférencement d'un pointeur de fonction fonctionne-t-il?

65
Lazer

Ce n'est pas tout à fait la bonne question. Pour C, au moins, la bonne question est

Qu'advient-il d'une valeur de fonction dans un contexte rvalue?

(Un contexte rvalue est n'importe où un nom ou une autre référence apparaît où il doit être utilisé comme valeur, plutôt que comme emplacement - essentiellement n'importe où sauf sur le côté gauche d'une affectation. Le nom lui-même vient de la droite - côté d'une tâche.)

OK, qu'arrive-t-il à une valeur de fonction dans un contexte rvalue? Il est immédiatement et implicitement converti en un pointeur vers la valeur de fonction d'origine. Si vous déréférencez ce pointeur avec *, vous obtenez à nouveau la même valeur de fonction, qui est immédiatement et implicitement convertie en pointeur. Et vous pouvez le faire autant de fois que vous le souhaitez.

Vous pouvez essayer deux expériences similaires:

  • Que se passe-t-il si vous déréférencez un pointeur de fonction dans un contexte lvalue - le côté gauche d'une affectation. (La réponse portera sur ce que vous attendez, si vous gardez à l'esprit que les fonctions sont immuables.)

  • Une valeur de tableau est également convertie en pointeur dans un contexte lvalue, mais elle est convertie en pointeur de type élément, pas en pointeur vers le tableau. Le déréférencer vous donnera donc un élément, pas un tableau, et la folie que vous montrez ne se produit pas.

J'espère que cela t'aides.

P.S. Quant à pourquoi une valeur de fonction est implicitement convertie en un pointeur, la réponse est que pour ceux qui utilisent des pointeurs de fonction, il est très pratique de ne pas avoir à utiliser & est partout. Il y a aussi une double commodité: un pointeur de fonction en position d'appel est automatiquement converti en valeur de fonction, vous n'avez donc pas à écrire * pour appeler via un pointeur de fonction.

P.P.S. Contrairement aux fonctions C, les fonctions C++ peuvent être surchargées, et je ne suis pas qualifié pour commenter le fonctionnement de la sémantique en C++.

50
Norman Ramsey

C++ 03 §4.3/1:

Une valeur l de type fonction T peut être convertie en une valeur r de type "pointeur vers T." Le résultat est un pointeur vers la fonction.

Si vous tentez une opération non valide sur une référence de fonction, telle que l'unaire * opérateur, la première chose que la langue essaie est une conversion standard. C'est comme convertir un int en l'ajoutant à un float. En utilisant * sur une référence de fonction fait que le langage prend son pointeur à la place, qui dans votre exemple, est le carré 1.

Un autre cas où cela s'applique est lors de l'attribution d'un pointeur de fonction.

void f() {
    void (*recurse)() = f; // "f" is a reference; implicitly convert to ptr.
    recurse(); // call operator is defined for pointers
}

Notez que cela ne fonctionne pas fonctionne dans l'autre sens.

void f() {
    void (&recurse)() = &f; // "&f" is a pointer; ERROR can't convert to ref.
    recurse(); // OK - call operator is *separately* defined for references
}

Les variables de référence de fonction sont agréables car elles (en théorie, je n'ai jamais testé) suggèrent au compilateur qu'une branche indirecte peut être inutile, si elle est initialisée dans une portée englobante.

En C99, le déréférencement d'un pointeur de fonction donne un désignateur de fonction. §6.3.2.1/4:

Un désignateur de fonction est une expression qui a un type de fonction. Sauf lorsqu'il s'agit de l'opérande de l'opérateur sizeof ou de l'opérateur unaire &, un désignateur de fonction de type "fonction renvoyant le type" est converti en une expression de type "pointeur vers la fonction renvoyant le type".

Cela ressemble plus à la réponse de Norman, mais notamment C99 n'a pas de concept de valeurs.

7
Potatoswatter

Mettez-vous à la place de l'auteur du compilateur. Un pointeur de fonction a une signification bien définie, c'est un pointeur vers un blob d'octets qui représentent le code machine.

Que faites-vous lorsque le programmeur déréférence un pointeur de fonction? Prenez-vous le premier (ou 8) octets du code machine et réinterprétez-le comme un pointeur? Les chances sont d'environ 2 milliards à un que cela ne fonctionnera pas. Déclarez-vous UB? Beaucoup de ce qui circule déjà. Ou ignorez-vous simplement la tentative? Vous connaissez la réponse.

2
Hans Passant

Comment fonctionne exactement le déréférencement d'un pointeur de fonction?

Deux étapes. La première étape est au moment de la compilation, la seconde au moment de l'exécution.

Dans la première étape, le compilateur voit qu'il a un pointeur et un contexte dans lequel ce pointeur est déréférencé (tel que (*pFoo)()) afin qu'il génère du code pour cette situation, code qui sera utilisé à l'étape 2.

À l'étape 2, lors de l'exécution, le code est exécuté. Le pointeur contient quelques octets indiquant quelle fonction doit être exécutée ensuite. Ces octets sont en quelque sorte chargés dans le CPU. Un cas courant est un CPU avec un CALL [register] instruction. Sur de tels systèmes, un pointeur de fonction peut être simplement l'adresse d'une fonction en mémoire, et le code de déréférencement ne fait rien de plus que de charger cette adresse dans un registre suivi d'un CALL [register] instruction.

1
MSalters