Question

J'ai eu une expérience récente avec les pointeurs de fonction en C.

Continuant ainsi avec la tradition de répondre à vos propres questions, j'ai décidé de faire un petit résumé des bases, pour ceux qui ont besoin d'une plongée rapide dans le sujet.

Était-ce utile?

La solution

Pointeurs de fonction en C

Commençons par une fonction de base sur laquelle nous allons pointer vers :

int addInt(int n, int m) {
    return n+m;
}

Tout d’abord, définissons un pointeur sur une fonction qui reçoit 2 int s et retourne un typedef:

int (*functionPtr)(int,int);

Nous pouvons maintenant indiquer notre fonction en toute sécurité:

functionPtr = &addInt;

Maintenant que nous avons un pointeur sur la fonction, utilisons-le:

int sum = (*functionPtr)(2, 3); // sum == 5

Passer le pointeur sur une autre fonction est fondamentalement le même:

int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

Nous pouvons également utiliser les pointeurs de fonction dans les valeurs de retour (essayez de suivre, cela devient compliqué):

// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

Mais il est beaucoup plus agréable d'utiliser un <=>:

typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}

Autres conseils

Les pointeurs de fonction en C peuvent être utilisés pour effectuer une programmation orientée objet en C.

Par exemple, les lignes suivantes sont écrites en C:

String s1 = newString();
s1->set(s1, "hello");

Oui, le -> et l'absence d'un opérateur new est un don mort, mais cela semble impliquer que nous définissions le texte de certaines String classes sur "hello".

En utilisant des pointeurs de fonction, il est possible d'émuler des méthodes en C .

Comment cela est-il accompli?

La classe struct est en réalité un newString avec un ensemble de pointeurs de fonction qui permettent de simuler des méthodes. Ce qui suit est une déclaration partielle de la getString classe:

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

Comme on peut le constater, les méthodes de la classe get sont en réalité des pointeurs de fonction vers la fonction déclarée. Lors de la préparation de l'instance de internal, la fonction s1->set("hello"); est appelée afin de configurer les pointeurs de fonction vers leurs fonctions respectives:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

Par exemple, la fonction s1->set(s1, "hello") appelée en appelant la méthode ImmutableString est définie comme suit:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

Une chose qui peut être remarquée est qu’il n’existe pas de concept d’instance d’objet et que les méthodes font en fait partie intégrante d’un objet, donc un & "self object &"; doit être transmis à chaque invocation. (Et le set est simplement un length masqué, omis précédemment de la liste de codes: c’est un moyen de masquer des informations, mais cela n’est pas pertinent pour les pointeurs de fonction.)

Ainsi, plutôt que de pouvoir faire char*, il faut passer l'objet pour effectuer l'action sur newImmutableString.

Cette explication mineure devant passer par une référence à vous-même, nous passons à la partie suivante, qui est l'héritage en C .

Disons que nous voulons créer une sous-classe de String.get, disons un String.length. Afin de rendre la chaîne immuable, la méthode base ne sera pas accessible, tout en conservant l'accès à 0 et à lengthOverrideMethod, et imposera le constructeur & "; &"; accepter un <=>:

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

En gros, pour toutes les sous-classes, les méthodes disponibles sont à nouveau des pointeurs de fonction. Cette fois, la déclaration de la méthode <=> n'est pas présente. Par conséquent, elle ne peut pas être appelée dans un <=>.

En ce qui concerne la mise en oeuvre de <=>, le seul code pertinent est le " constructeur " la fonction, le <=>:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

Lors de l'instanciation de <=>, les pointeurs de fonction sur les méthodes <=> et <=> font en réalité référence aux méthodes <=> et <=>, en passant par la variable <=> qui est une variable stockée en interne < => objet.

L'utilisation d'un pointeur de fonction peut permettre l'héritage d'une méthode d'une super-classe.

Nous pouvons continuer à polymorphisme dans C .

Si, par exemple, nous voulions changer le comportement de la méthode <=> pour retourner <=> tout le temps dans la classe <=> pour une raison quelconque, il suffirait de:

  1. Ajouter une fonction qui servira de méthode <=> de substitution.
  2. Accédez au " constructeur " et définissez le pointeur de fonction sur la <=> méthode de substitution.

L'ajout d'une méthode <=> de substitution dans <=> peut être effectué en ajoutant un <=>:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

Ensuite, le pointeur de fonction pour la méthode <=> dans le constructeur est relié au <=>:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

Maintenant, plutôt que d'avoir un comportement identique pour la méthode <=> de la classe <=> et la <=> classe, la méthode <=> se réfère maintenant au comportement défini dans la fonction <=>.

Je dois ajouter un avertissement selon lequel j'apprends encore à écrire avec un style de programmation orienté objet en C, il y a donc probablement des points que je n'ai pas bien expliqués ou que je ne suis peut-être pas à la hauteur en termes de meilleure façon mettre en œuvre la POO en C. Mais mon but était d'essayer d'illustrer l'une des nombreuses utilisations des pointeurs de fonction.

Pour plus d'informations sur la procédure de programmation orientée objet en C, reportez-vous aux questions suivantes:

Le guide pour se faire virer: comment abuser des pointeurs de fonction dans GCC sur des machines x86 en compilant votre code manuellement:

Ces littéraux de chaîne sont des octets de code machine x86 32 bits. 0xC3 est une une instruction x86 ret .

Normalement, vous ne les écririez pas manuellement, vous utiliseriez un assembleur tel que nasm pour l'assembler en un binaire plat que vous hexdumperiez dans un littéral de chaîne C.

  1. Renvoie la valeur actuelle du registre EAX

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. écrire une fonction d'échange

    int a = 10, b = 20;
    ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
    
  3. Ecrivez un compteur de boucles for à 1000, appelant une fonction à chaque fois

    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
    
  4. Vous pouvez même écrire une fonction récursive comptant jusqu'à 100

    const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
    i = ((int(*)())(lol))(lol);
    

Notez que les compilateurs placent les littéraux de chaîne dans la section .rodata (ou .rdata sous Windows), qui est liée au segment de texte (avec le code des fonctions).

Le segment de texte dispose de l'autorisation Read + Exec. Par conséquent, la conversion de littéraux de chaîne en pointeurs de fonction fonctionne sans requérir d'appels système mprotect() ou VirtualProtect() comme vous auriez besoin d'une mémoire allouée dynamiquement. (Ou gcc -z execstack lie le programme avec une pile + un segment de données + un exécutable de tas, comme un hack rapide.)

Pour les désassembler, vous pouvez les compiler pour mettre une étiquette sur les octets et utiliser un désassembleur.

// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";

En compilant avec gcc -c -m32 foo.c et en désassemblant avec objdump -D -rwC -Mintel, nous pouvons obtenir l'assembly et découvrir que ce code enfreint le ABI en écrasant EBX (un registre à préservation d'appel) et qu'il est généralement inefficace.

00000000 <swap>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack
   4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b
   8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *a
   a:   8b 1b                   mov    ebx,DWORD PTR [ebx]
   c:   31 c3                   xor    ebx,eax                # pointless xor-swap
   e:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers
  10:   31 c3                   xor    ebx,eax
  12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack
  16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a
  18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]
  1c:   89 19                   mov    DWORD PTR [ecx],ebx
  1e:   c3                      ret    

  not shown: the later bytes are ASCII text documentation
  they're not executed by the CPU because the ret instruction sends execution back to the caller

Ce code machine fonctionnera (probablement) en code 32 bits sous Windows, Linux, OS X, etc.: les conventions d’appel par défaut de tous ces systèmes d’exploitation transmettent les arguments sur la pile au lieu d’être plus efficaces dans les registres. Mais EBX est préservé dans toutes les conventions d’appel habituelles. Par conséquent, son utilisation en tant que registre de travail sans sauvegarde / restauration peut facilement provoquer le blocage de l’appelant.

L’une de mes utilisations préférées des pointeurs de fonction est la possibilité de faire des itérateurs faciles et peu coûteux -

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}

Les pointeurs de fonction deviennent faciles à déclarer une fois que vous avez les déclarateurs de base:

  • id: ID: ID est un
  • Pointeur: *D: D pointeur sur
  • Fonction: D(<parameters>): Fonction D prenant < paramètres > renvoyant

Alors que D est un autre déclarateur construit en utilisant ces mêmes règles. En fin de compte, quelque part, il se termine par [ (voir ci-dessous pour un exemple), qui correspond au nom de l'entité déclarée. Essayons de construire une fonction prenant un pointeur sur une fonction ne prenant rien et retournant int, et retournant un pointeur sur une fonction prenant un caractère et retournant int. Avec les caractères typographiques, c'est comme ça

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

Comme vous le voyez, il est assez facile de le construire à l’aide de typedefs. Sans typedefs, ce n'est pas difficile non plus avec les règles de déclaration ci-dessus, appliquées de manière cohérente. Comme vous le voyez, j'ai oublié la partie sur laquelle pointe le pointeur et la chose retournée par la fonction. C'est ce qui apparaît à l'extrême gauche de la déclaration et n'a aucun intérêt: il est ajouté à la fin si on a déjà construit le déclarant. Faisons cela. Construisez-le de manière cohérente, premier mot - affiche la structure avec ] et D1:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

Comme vous le voyez, on peut décrire un type complètement en ajoutant des déclarants les uns après les autres. La construction peut être faite de deux manières. L'une est ascendante, commence par la bonne chose (feuilles) et continue jusqu'à l'identifiant. L’autre moyen est de haut en bas, en partant de l’identificateur, en descendant vers les feuilles. Je montrerai les deux.

De bas en haut

La construction commence par la chose à droite: la chose retournée, qui est la fonction prenant char. Pour que les déclarants soient distincts, je vais les numéroter:

D1(char);

Insère le paramètre char directement, car il est trivial. Ajouter un pointeur au déclarateur en remplaçant *D2 par *-operator. Notez que nous devons placer des parenthèses autour de (). On peut le savoir en consultant la priorité de *(D2(char p)) et de l'opérateur d'appel de fonction D2. Sans nos parenthèses, le compilateur le lirait comme <parameters>. Mais ce ne serait évidemment plus un simple remplacement de D1 par D3(<parameters>). Les parenthèses sont toujours autorisées autour des déclarants. Donc, vous ne faites rien de mal si vous en ajoutez trop, en fait.

(*D2)(char);

Le type de retour est complet! Remplaçons maintenant D3 par le déclarateur de fonction en prenant char renvoyant , qui est void ce à quoi nous en sommes maintenant.

(*D3(<parameters>))(char)

Notez qu'aucune parenthèse n'est nécessaire, car nous voulons ID1 être un déclarateur de fonction et non un déclarateur de pointeur cette fois. Génial, il ne reste que les paramètres pour cela. Le paramètre est identique à celui du type de retour, avec int remplacé par ID0. Donc, je vais le copier:

(*D3(   (*ID1)(void)))(char)

J'ai remplacé * par (char), car nous en avons terminé avec ce paramètre (c'est déjà un pointeur sur une fonction - aucun autre déclarant n'est nécessaire). <=> sera le nom du paramètre. Maintenant, j'ai dit plus haut, à la fin, on ajoute le type modifié par tous les déclarateurs - celui qui apparaît à l'extrême gauche de chaque déclaration. Pour les fonctions, cela devient le type de retour. Pour les pointeurs, le type pointé, etc ... C'est intéressant quand on écrit le type, il apparaîtra dans l'ordre inverse, à l'extrême droite :) Quoi qu'il en soit, le remplacer par la déclaration complète. Les deux fois <=> bien sûr.

int (*ID0(int (*ID1)(void)))(char)

J'ai appelé l'identifiant de la fonction <=> dans cet exemple.

De haut en bas

Cela commence à l'identifiant tout à gauche dans la description du type, en enveloppant ce déclarant alors que nous parcourons le chemin à droite. Commencez par la fonction prenant <=> paramètres <=> retour

ID0(<parameters>)

La prochaine chose dans la description (après & "; renvoyer &";) était pointeur sur . Intégrons-le:

*ID0(<parameters>)

La prochaine chose à faire était une fonction prenant <=> paramètres <=> renvoyant . Le paramètre est un simple caractère, donc nous le remettons tout de suite, car c'est rvraiment trivial.

(*ID0(<parameters>))(char)

Notez les parenthèses que nous avons ajoutées, car nous souhaitons à nouveau que le <=> se lie d’abord, puis au le <=>. Sinon, la fonction prendrait <=> les paramètres <=> renvoyer la fonction ... . Non, les fonctions renvoyant des fonctions ne sont même pas autorisées.

Il ne reste plus qu'à mettre <=> paramètres <=>. Je vais vous montrer une version abrégée de la dérivation, car je pense que vous savez déjà comment procéder.

pointer to: *ID1
... function taking void returning: (*ID1)(void)

Il suffit de mettre <=> avant les déclarants, comme nous l'avons fait avec la méthode ascendante, et nous avons terminé

int v = (*ID0(some_function_pointer))(some_char);

La bonne chose

Est-ce que la méthode ascendante ou descendante est préférable? J'ai l'habitude de faire du bas vers le haut, mais certaines personnes peuvent être plus à l'aise avec le haut vers le bas. C'est une question de goût, je pense. Incidemment, si vous appliquez tous les opérateurs de cette déclaration, vous obtiendrez un int:

<*>

C’est une propriété intéressante des déclarations en C: la déclaration affirme que si ces opérateurs sont utilisés dans une expression utilisant l’identificateur, le type est renvoyé à l'extrême gauche. C'est comme ça pour les tableaux aussi.

J'espère que vous avez aimé ce petit tutoriel! Nous pouvons maintenant établir un lien avec ceci lorsque des personnes s'interrogent sur l'étrange syntaxe de déclaration des fonctions. J'ai essayé de mettre le moins possible de composants internes. N'hésitez pas à éditer / corriger les choses en elle.

Un autre bon usage pour les pointeurs de fonction:
Basculer d'une version à l'autre sans douleur

Ils sont très pratiques à utiliser lorsque vous souhaitez différentes fonctions à différents moments ou différentes phases de développement. Par exemple, je développe une application sur un ordinateur hôte doté d’une console, mais la version finale du logiciel sera placée sur un Avnet ZedBoard (doté de ports pour les affichages et les consoles, mais ils ne sont pas nécessaires / recherchés pour la version finale). Par conséquent, lors du développement, je vais utiliser printf pour afficher les messages d'état et d'erreur, mais une fois terminé, je ne souhaite plus rien imprimer. Voici ce que j'ai fait:

version.h

// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

Dans version.c je définirai les 2 prototypes de fonctions présents dans version.h

version.c

#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

Notez comment le pointeur de fonction est prototypé dans void (* zprintf)(const char *, ...); comme

board_init()

lorsqu'il est référencé dans l'application, il démarre. exécuter partout où il pointe, ce qui reste à définir.

Sous zprintf, vous remarquerez dans la zprintf = &printf; fonction où zprintf = &noprint; est affectée une fonction unique (dont la signature de fonction correspond) en fonction de la version définie dans <=>

<= > zprintf appelle printf à des fins de débogage

ou

<=> zprintf ne fait que revenir et n'exécutera pas de code inutile.

L'exécution du code ressemblera à ceci:

mainProg.c

#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

Le code ci-dessus utilisera <=> en mode débogage ou ne fera rien s'il est en mode publication. C'est beaucoup plus facile que de parcourir l'ensemble du projet et de commenter ou supprimer du code. Il ne me reste plus qu'à changer de version dans <=> et le code fera le reste!

Le pointeur de fonction est généralement défini par typedef et utilisé comme paramètre & amp; valeur de retour.

Les réponses ci-dessus ont déjà beaucoup expliqué, je viens de donner un exemple complet:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

L’une des grandes utilisations des pointeurs de fonction en C est d’appeler une fonction sélectionnée au moment de l’exécution. Par exemple, la bibliothèque d'exécution C comporte deux routines, qsort . et bsearch , qui renvoient un pointeur sur une fonction appelée comparer deux éléments en cours de tri; Cela vous permet de trier ou de rechercher, n'importe quoi, en fonction des critères que vous souhaitez utiliser.

Un exemple très basique, s'il existe une fonction appelée print(int x, int y) qui peut à son tour nécessiter l'appel d'une fonction (add() ou sub(), qui sont du même type), alors ce que nous allons faire, ajoutera un argument de pointeur de fonction à la print() fonction, comme indiqué ci-dessous:

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

Le résultat est:

  

valeur est: 410
  la valeur est: 390

Un pointeur de fonction est une variable qui contient l'adresse d'une fonction. Puisqu'il s'agit d'une variable de pointeur avec certaines propriétés restreintes, vous pouvez l'utiliser à peu près comme toute autre variable de pointeur dans les structures de données.

La seule exception à laquelle je peux penser est de traiter le pointeur de fonction comme désignant autre chose qu'une valeur unique. Faire de l'arithmétique de pointeur en incrémentant ou décrémentant un pointeur de fonction ou en ajoutant / soustrayant un décalage à un pointeur de fonction n'a pas vraiment d'utilité, car un pointeur de fonction ne pointe que sur une seule chose, le point d'entrée d'une fonction.

La taille d'une variable de pointeur de fonction, le nombre d'octets occupés par la variable, peuvent varier en fonction de l'architecture sous-jacente, par ex. x32 ou x64 ou autre chose.

La déclaration d'une variable de pointeur de fonction doit spécifier le même type d'informations qu'une déclaration de fonction pour que le compilateur C puisse effectuer les types de vérifications habituels. Si vous ne spécifiez pas de liste de paramètres dans la déclaration / définition du pointeur de fonction, le compilateur C ne pourra pas vérifier l'utilisation des paramètres. Il existe des cas où ce manque de vérification peut être utile, mais n'oubliez pas qu'un filet de sécurité a été supprimé.

Quelques exemples:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

Les deux premières déclarations sont assez similaires en ce sens que:

  • func est une fonction qui prend un int et un char * et retourne un pFunc
  • func() est un pointeur de fonction auquel est attribuée l'adresse d'une fonction qui prend un pFunc = func; et un if et retourne un void

Donc, à partir de ce qui précède, nous pourrions avoir une ligne source dans laquelle l'adresse de la fonction namespace est affectée à la variable pointeur de la fonction struct comme dans static.

Notez la syntaxe utilisée avec une déclaration / définition de pointeur de fonction dans laquelle les parenthèses sont utilisées pour s'affranchir des règles de priorité des opérateurs naturels.

int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

Plusieurs exemples d'utilisation différents

Quelques exemples d'utilisation d'un pointeur de fonction:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

Vous pouvez utiliser des listes de paramètres de longueur variable dans la définition d'un pointeur de fonction.

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

Sinon, vous ne pouvez pas spécifier une liste de paramètres. Cela peut être utile mais élimine la possibilité pour le compilateur C d'effectuer des vérifications sur la liste d'arguments fournie.

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

Incantations de style C

Vous pouvez utiliser des conversions de style C avec des pointeurs de fonction. Cependant, sachez qu'un compilateur C peut être laxiste en matière de vérification ou fournir des avertissements plutôt que des erreurs.

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

Comparer le pointeur de fonction à l'égalité

Vous pouvez vérifier qu'un pointeur de fonction est égal à une adresse de fonction particulière en utilisant une instruction const bien que je ne sois pas sûr de son utilité. Les autres opérateurs de comparaison semblent avoir encore moins d’utilité.

static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

Tableau de pointeurs de fonction

Et si vous souhaitez disposer d'un tableau de pointeurs de fonction, chacun des éléments pour lesquels la liste d'arguments présente des différences, vous pouvez définir un pointeur de fonction avec la liste d'arguments non spécifiée (pas qsort(), ce qui signifie aucun argument, mais uniquement non spécifié). quelque chose comme ce qui suit, même si vous pouvez voir des avertissements du compilateur C. Cela fonctionne également pour un paramètre de pointeur de fonction sur une fonction:

int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

Style C bsearch() Utilisation de Global <=> avec les pointeurs de fonction

Vous pouvez utiliser le mot clé <=> pour spécifier une fonction dont le nom correspond à la portée du fichier, puis l'affecter à une variable globale afin de fournir quelque chose de similaire à la <=> fonctionnalité de C ++.

Dans un fichier d'en-tête, définissez une structure qui sera notre espace de noms avec une variable globale qui l'utilise.

typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

Puis dans le fichier source C:

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

Ceci serait alors utilisé en spécifiant le nom complet de la variable de structure globale et le nom du membre pour accéder à la fonction. Le modificateur <=> est utilisé sur le global pour qu’il ne puisse pas être modifié par accident.

int abcd = FuncThingsGlobal.func1 (a, b);

Domaines d'application des pointeurs de fonction

Un composant de bibliothèque DLL peut effectuer quelque chose de similaire à l'approche de style C <=> dans laquelle une interface de bibliothèque particulière est demandée à une méthode factory dans une interface de bibliothèque prenant en charge la création d'un <=> contenant des pointeurs de fonction .. L’interface de la bibliothèque charge la version de la DLL demandée, crée une structure avec les pointeurs de fonction nécessaires, puis renvoie la structure à l’appelant demandeur pour l’utiliser.

typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

et cela pourrait être utilisé comme dans:

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

La même approche peut être utilisée pour définir une couche matérielle abstraite pour un code utilisant un modèle particulier du matériel sous-jacent. Les pointeurs de fonction sont dotés de fonctions spécifiques au matériel par une usine afin de fournir la fonctionnalité spécifique au matériel qui implémente les fonctions spécifiées dans le modèle matériel abstrait. Ceci peut être utilisé pour fournir une couche matérielle abstraite utilisée par un logiciel qui appelle une fonction fabrique afin d'obtenir l'interface de fonction matérielle spécifique, puis utilise les pointeurs de fonction fournis pour effectuer des actions sur le matériel sous-jacent sans avoir besoin de connaître les détails d'implémentation de la cible spécifique. .

Pointeurs de fonction pour créer des délégués, des gestionnaires et des rappels

Vous pouvez utiliser les pointeurs de fonction pour déléguer une tâche ou une fonctionnalité. L'exemple classique en C est le pointeur de fonction de délégué de comparaison utilisé avec les fonctions <=> et <=> de la bibliothèque Standard C pour fournir l'ordre de classement permettant de trier une liste d'éléments ou d'effectuer une recherche binaire sur une liste d'éléments triée. Le délégué de la fonction de comparaison spécifie l’algorithme de classement utilisé dans le tri ou la recherche binaire.

Une autre utilisation est similaire à l'application d'un algorithme à un conteneur C ++ Standard Template Library.

void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

Un autre exemple concerne le code source de l'interface graphique dans lequel un gestionnaire pour un événement particulier est enregistré en fournissant un pointeur de fonction qui est réellement appelé lorsque l'événement se produit. La structure Microsoft MFC avec ses mappes de messages utilise quelque chose de similaire pour gérer les messages Windows remis à une fenêtre ou à un thread.

Les fonctions asynchrones nécessitant un rappel sont similaires à un gestionnaire d’événements. L'utilisateur de la fonction asynchrone appelle la fonction asynchrone pour démarrer une action et fournit un pointeur de fonction que la fonction asynchrone appellera une fois l'action terminée. Dans ce cas, l'événement est la fonction asynchrone terminant sa tâche.

La fonction de démarrage à partir de zéro a une adresse mémoire quelconque à partir de laquelle ils commencent à s'exécuter. Dans le langage assembleur, ils sont appelés comme (appelez & "; L'adresse mémoire de la fonction &"); Revenez maintenant à C Si la fonction a une adresse mémoire, elle peut être manipulée par des pointeurs dans C. Par les règles de C

1.Vous devez d'abord déclarer un pointeur pour qu'il fonctionne 2.Passe l'adresse de la fonction souhaitée

**** Remarque - > les fonctions doivent être du même type ****

Ce programme simple illustrera chaque chose.

#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

entrer la description de l'image ici Après cela permet de voir comment la machine le comprend. Glimpse des instructions machine du programme ci-dessus en architecture 32 bits.

La zone marquée en rouge indique comment l’adresse est échangée et stockée dans eax. Ensuite, leur est une instruction d'appel sur eax. eax contient l'adresse souhaitée de la fonction.

Les pointeurs de fonction étant souvent des rappels dactylographiés, vous pouvez consulter les rappels de type sécurisés . Il en va de même pour les points d’entrée, etc. des fonctions qui ne sont pas des rappels.

C est assez instable et pardonne en même temps:)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top