web-dev-qa-db-fra.com

Pointeurs de fonction en C - nature et utilisation

Je viens de lire une question intéressante ici qui me fait me demander encore deux choses:

  1. Pourquoi devrait-on comparer des pointeurs de fonction, étant donné que par conception, l'unicité des fonctions est assurée par leurs différents noms?
  2. Le compilateur voit-il les pointeurs de fonction comme des pointeurs spéciaux? Je veux dire, les voit-il comme, disons, des pointeurs vers void * ou contient-il des informations plus riches (comme le type de retour, le nombre d'arguments et les types d'arguments?)
32
Benjamin Barrois
  1. Pourquoi quelqu'un comparerait-il des pointeurs? Considérez le scénario suivant -

    Vous disposez d'un tableau de pointeurs de fonction, disons qu'il s'agit d'une chaîne de rappel et que vous devez appeler chacun d'eux. La liste se termine par un pointeur de fonction NULL (ou sentinelle). Vous devez comparer si vous avez atteint la fin de la liste en comparant avec ce pointeur sentinelle. En outre, ce cas justifie la préoccupation des PO précédents selon laquelle différentes fonctions devraient avoir des pointeurs différents même s'ils sont similaires.

  2. Le compilateur les voit-il différemment? Oui. Les informations de type incluent toutes les informations sur les arguments et le type de retour.

    Par exemple, le code suivant sera/devrait être rejeté par le compilateur -

    void foo(int a);
    void (*bar)(long) = foo; // Without an explicit cast
    
20

Pourquoi devrait-on comparer des pointeurs de fonction? Voici un exemple:

#include <stdbool.h>

/*
 * Register a function to be executed on event. A function may only be registered once.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful registration, false if the function is already registered.
 */
bool register_function_for_event(void (*arg)(void));

/*
 * Un-register a function previously registered for execution on event.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful un-registration, false if the function was not registered.
 */
bool unregister_function_for_event(void (*arg)(void));

Le corps de register_function_for_event ne voit que arg. Il ne voit aucun nom de fonction. Il doit comparer les pointeurs de fonction pour signaler que quelqu'un enregistre deux fois la même fonction.

Et si vous voulez soutenir quelque chose comme unregister_function_for_event pour compléter ce qui précède, la seule information dont vous disposez est l'adresse de la fonction. Vous devrez donc à nouveau le transmettre et le comparer pour permettre le retrait.

Quant aux informations plus riches, oui. Lorsque le type de fonction contient un prototype, il fait partie des informations de type statique. Rappelez-vous qu'en C, un pointeur de fonction peut être déclaré sans prototype, mais c'est une fonction obsolète.

37
StoryTeller
  1. Pourquoi devrait-on comparer des pointeurs de fonction, étant donné que par conception, l'unicité des fonctions est assurée par leurs différents noms?

Un pointeur de fonction peut pointer vers différentes fonctions à différents moments d'un programme.

Si vous avez une variable telle que

void (*fptr)(int);

il peut pointer vers n'importe quelle fonction qui accepte un int en entrée et renvoie void.

Disons que vous avez:

void function1(int)
{
}

void function2(int)
{
}

Vous pouvez utiliser:

fptr = function1;
foo(fptr);

ou:

fptr = function2;
foo(fptr);

Vous voudrez peut-être faire différentes choses dans foo selon que fptr pointe vers une fonction ou une autre. D'où la nécessité:

if ( fptr == function1 )
{
    // Do stuff 1
}
else
{
    // Do stuff 2
}
  1. Le compilateur voit-il les pointeurs de fonction comme des pointeurs spéciaux? Je veux dire, les voit-il comme, disons, des pointeurs vers void * ou contient-il des informations plus riches (comme le type de retour, le nombre d'arguments et les types d'arguments?)

Oui, les pointeurs de fonction sont des pointeurs spéciaux, différents des pointeurs qui pointent vers des objets.

Le type d'un pointeur de fonction contient toutes ces informations au moment de la compilation. Par conséquent, donnez un pointeur de fonction, le compilateur aura toutes ces informations - le type de retour, le nombre d'arguments et leurs types.

19
R Sahu

La partie classique sur les pointeurs de fonction est déjà discutée dans la réponse d'un autre:

  • Comme les autres pointeurs, les pointeurs pour fonctionner peuvent pointer vers différents objets à différents moments, donc les comparer peut avoir un sens.
  • Les pointeurs vers la fonction sont spéciaux et ne doivent pas être stockés dans d'autres types de pointeurs (pas même void * et même en langage C).
  • La partie riche (signature de fonction) est stockée dans le type de fonction - la raison de la phrase ci-dessus.

Mais C a un mode de déclaration de fonction (hérité). En plus du mode prototype complet qui déclare le type de retour et le type de tous les paramètres, C peut utiliser le soi-disant liste de paramètres mode qui est l'ancien Mode K&R. Dans ce mode, la déclaration déclare uniquement le type de retour:

int (*fptr)();

En C, il déclare un pointeur sur la fonction retournant un int et acceptant des paramètres arbitraires. Simplement, ce sera un comportement non défini (UB) de l'utiliser avec une mauvaise liste de paramètres.

Voici donc le code C légal:

#include <stdio.h>
#include <string.h>

int add2(int a, int b) {
    return a + b;
}
int add3(int a, int b, int c) {
    return a + b + c;
}

int(*fptr)();
int main() {
    fptr = add2;
    printf("%d\n", fptr(1, 2));
    fptr = add3;
    printf("%d\n", fptr(1, 2, 3));
    /* fprintf("%d\n", fptr(1, 2)); Would be UB */
    return 0;
}

Ne prétendez pas que je vous ai conseillé de le faire! Il est désormais considéré comme une fonction obsolète et doit être évité. Je vous mets simplement en garde contre cela. À mon humble avis, il ne pouvait avoir que des cas d'utilisation acceptables exceptionnels.

5
Serge Ballesta

1) Il y a beaucoup de situations. Prenons par exemple l'implémentation typique d'une machine à états finis:

typedef void state_func_t (void);

const state_func_t* STATE_MACHINE[] =
{
  state_init,
  state_something,
  state_something_else
};

...

for(;;)
{
  STATE_MACHINE[state]();
}

Vous devrez peut-être inclure du code supplémentaire dans l'appelant pour une situation spécifique:

if(STATE_MACHINE[state] == state_something)
{
  print_debug_stuff();
}

2) Oui, le compilateur C les considère comme des types distincts. En fait, les pointeurs de fonction ont une sécurité de type plus stricte que les autres types de C, car ils ne peuvent pas être implicitement convertis vers/depuis void*, comme les pointeurs vers les types d'objets peuvent. (C11 6.3.2.3/1). Ils ne peuvent pas non plus être explicitement castés vers/depuis void* - cela invoquerait des extensions non standard.

Tout dans le pointeur de fonction est important pour déterminer son type: le type de la valeur de retour, le type des paramètres et le nombre de paramètres. Tous ces éléments doivent correspondre, ou deux pointeurs de fonction ne sont pas compatibles.

2
Lundin

Imaginez comment vous implémenteriez une fonctionnalité similaire à celle de WNDCLASS ?

Il a un lpszClassName pour distinguer les classes de fenêtres les unes des autres, mais disons que vous n'aviez pas besoin (ou n'aviez pas) de chaîne disponible pour distinguer les différentes classes les unes des autres.

Ce que vous avez est la procédure de classe de fenêtre lpfnWndProc (de type WindowProc ) .

Alors maintenant, que feriez-vous si quelqu'un appelait RegisterClass deux fois avec le même lpfnWndProc?
Vous devez en quelque sorte détecter les réinscriptions de la même classe et renvoyer une erreur.

C'est un cas où la chose logique à faire est de comparer les fonctions de rappel.

2
Mehrdad

Les pointeurs de fonction sont des variables. Pourquoi devrait-on comparer des variables, étant donné que par concept, l'unicité des variables est assurée par leurs différents noms? Eh bien, parfois deux variables peuvent avoir la même valeur et vous voulez savoir si c'est le cas.

C considère les pointeurs vers les fonctions avec la même liste d'arguments et la valeur de retour comme étant du même type.

1
Dmitry Grigoryev