Comment fonctionnent les indicateurs de fonction en C?

j'ai eu une certaine expérience de ces derniers temps avec des pointeurs de fonction en C.

poursuivant 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 sur le sujet.

1027
demandé sur Yuval Adam 2009-05-08 19:49:17
la source

12 ответов

pointeurs de fonction en C

commençons par une fonction de base que nous serons pointant vers :

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

première chose, définissons un pointeur vers une fonction qui reçoit 2 int s et renvoie un int :

int (*functionPtr)(int,int);

maintenant, nous pouvons sans danger pointer vers notre fonction:

functionPtr = &addInt;

maintenant que nous avons un pointeur vers la fonction, utilisons it:

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

passer le pointeur à une autre fonction est fondamentalement la même:

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

nous pouvons utiliser des pointeurs de fonction dans les valeurs de retour aussi bien (essayer de suivre, il obtient désordre):

// 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 :

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;
}
1264
répondu Yuval Adam 2016-12-30 01:23:52
la source

les pointeurs de fonction en C peuvent être utilisés pour effectuer la 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 give away mort, mais il semble sûr que nous sommes en train de définir le texte d'une certaine classe String pour être "hello" .

en utilisant des pointeurs de fonction, il est possible de reproduire des méthodes dans C .

comment cela se fait-il?

la classe String est en fait un struct avec un tas de pointeurs de fonction qui agissent comme un moyen de simuler des méthodes. Ce qui suit est une déclaration partielle de la classe String :

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 voir, les méthodes de la classe String sont en fait des pointeurs de fonction vers le fonction. Dans la préparation de l'instance du String , la fonction newString est appelée afin de mettre en place les indicateurs de fonction à 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 getString appelée en invoquant la méthode get est définie comme suit:

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

Une chose qui peut être remarqué, c'est qu'il n'y a pas de concept d'une instance d'un objet et avoir des méthodes qui sont en fait une partie d'un objet, donc une "auto objet" doit être transmise à chaque invocation. (Et le internal est juste un struct caché qui a été omis de la liste de code plus tôt -- c'est une façon d'effectuer la dissimulation de l'information, mais ce n'est pas pertinent pour les pointeurs de fonction.)

donc , plutôt que de pouvoir faire s1->set("hello"); , il faut passer dans l'objet pour effectuer l'action sur s1->set(s1, "hello") .

avec cette petite explication devant passer dans une référence à vous-même hors du chemin, nous allons passer à la partie suivante, qui est héritage dans C .

disons que nous voulons faire une sous-classe de String , disons un ImmutableString . Afin de rendre la chaîne immuable, la méthode set ne sera pas accessible, tout en maintenant l'accès à get et length , et forcera le" constructeur "à accepter un char* :

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);

fondamentalement, pour toutes les sous-classes, les méthodes disponibles sont de nouveau des pointeurs de fonction. Cette fois, la déclaration pour la méthode set n'est pas présente, donc, elle ne peut pas être appelée dans un ImmutableString .

en ce qui concerne la mise en œuvre du ImmutableString , le seul code pertinent est la fonction "constructeur", le newImmutableString :

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;
}

en instanciant le ImmutableString , les pointeurs de fonction à la Les méthodes get et length se réfèrent en fait à la méthode String.get et String.length , en passant par la variable base qui est un objet stocké en interne String .

L'utilisation d'un pointeur de fonction peut atteindre l'héritage d'une méthode à partir d'une superclasse.

Nous ne pouvons plus continuer à polymorphisme en C .

si par exemple nous voulions changer le comportement de la méthode length pour retourner 0 tout le temps dans la classe ImmutableString pour une raison quelconque, tout ce qui devrait être fait est à:

  1. ajouter une fonction qui va servir comme la méthode length prépondérante.
  2. allez sur le" constructeur "et réglez le pointeur de fonction sur la méthode length .

ajout d'une length méthode dans ImmutableString peut être effectué en ajoutant un lengthOverrideMethod :

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

ensuite, le pointeur de fonction pour la méthode length dans le constructeur est relié au lengthOverrideMethod :

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 length dans la classe ImmutableString comme la classe String , maintenant la méthode length se référera au comportement défini dans la fonction lengthOverrideMethod .

je dois ajouter un disclaimer que je suis encore en train d'apprendre à écrire avec un style de programmation orienté objet en C, donc il y a probablement des points que je n'ai pas bien expliqués, ou peut-être juste être hors de propos en termes de comment mettre en œuvre au mieux OOP en C. Mais mon but était d'essayer d'illustrer une des nombreuses utilisations des pointeurs de fonction.

pour plus d'information sur la façon d'exécuter la programmation orientée objet en C, veuillez vous référer aux questions suivantes:

270
répondu coobird 2017-05-23 15:18:30
la source

Le guide de licenciement: Comment l'abus des pointeurs de fonction dans GCC sur des machines x86 par la compilation de votre code à la main:

ces chaînes littérales sont des octets de code machine 32-bit x86. 0xC3 est x86 ret instruction .

vous n'écririez pas normalement à la main, vous écrivez en langage assembleur et puis utilisez un assembleur comme nasm pour l'assembler dans un binaire plat que vous hexdump en chaîne c littérale.

  1. retourne la valeur courante sur le 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. Ecrire un compteur pour boucle à 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 qui compte 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);
    

Note que les compilateurs place littéraux de chaîne dans la .rodata (ou .rdata sous Windows), qui est liée dans le cadre du segment de texte (avec code pour les fonctions).

le segment de texte a la permission de lire+Exec, donc le moulage littéral de chaîne de caractères pour les pointeurs de fonction fonctionne sans avoir besoin de mprotect() ou VirtualProtect() appels système comme vous auriez besoin pour la mémoire allouée dynamiquement. (Ou gcc -z execstack relie le programme avec stack + data segment + heap exécutable, comme un piratage rapide.)


pour les démonter, vous pouvez compiler ceci pour mettre une étiquette sur les octets, et utiliser un démonteur.

// 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";

compilant avec gcc -c -m32 foo.c et démontant avec objdump -D -rwC -Mintel , nous pouvons obtenir l'assemblage, et découvrir que ce code viole L'ABI en cloquant EBX (un registre d'appel préservé) et 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 sur Windows, Linux, OS X, et ainsi de suite: les conventions d'appel par défaut sur tous ces os passent args sur la pile au lieu de plus efficacement dans les registres. Mais EBX est préservé dans toutes les conventions d'appel normales, donc l'utiliser comme un scratch register sans le sauvegarder/le restaurer peut facilement faire planter l'appelant.

191
répondu Lee 2018-10-09 16:58:10
la source

une de mes utilisations préférées pour les pointeurs de fonction est aussi bon marché et facile itérateurs -

#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);
}
97
répondu Nick 2012-07-24 20:40:57
la source

les indicateurs de fonction deviennent faciles à déclarer une fois que vous avez les déclarants de base:

  • id: ID : ID est
  • pointeur: *D : pointeur D
  • fonction: D(<parameters>) : d fonction taking < parameters > returning

alors que D est un autre déclarant construit en utilisant ces mêmes règles. À la fin, quelque part, il se termine par ID (voir ci-dessous pour un exemple), qui est le nom de l'entité déclarée. Essayons de construire une fonction en prenant un pointeur vers une fonction en ne prenant rien et en retournant int, et en retournant un pointeur vers une fonction en prenant un char et en retournant int. Avec type-defs 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, il n'est pas difficile soit avec les règles déclaratrices ci-dessus, appliqué de manière cohérente. Comme vous le voyez j'ai raté la partie sur laquelle pointe le pointeur, et la chose la fonction retourne. C'est ce qui apparaît à l'extrême gauche de la déclaration, et n'est pas d'intérêt: Il est ajouté à la fin si l'on construit le constate déjà. Faisons-le. Le construire de manière cohérente, premier wordy-montrant la structure en utilisant [ et ] :

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

comme vous voyez, on peut décrire un type tout à fait en ajoutant les déclarants l'un après l'autre. La Construction peut se faire de deux façons. L'une est ascendante, commençant par la bonne chose (les feuilles) et se prolongeant jusqu'à l'identificateur. L'autre voie est descendante, en commençant par l'identificateur, en descendant jusqu'aux feuilles. Je montrerai les deux côtés.

Bottom Up

Construction commence par la chose à droite: la chose retournée, qui est la fonction prenant char. De gardez les déclarants distincts, je vais les numéroter:

D1(char);

a inséré le paramètre char directement, car il est trivial. Ajouter un indicateur au déclarant en remplaçant D1 par *D2 . Notez que nous devons envelopper les parenthèses autour de *D2 . On peut le savoir en regardant vers le haut de la priorité du *-operator et l'opérateur d'appel de fonction () . Sans nos parenthèses, le compilateur le lirait comme *(D2(char p)) . Mais que ne serait plus un simple remplacement de D1 par *D2 , bien sûr. Les parenthèses sont toujours permises autour des déclarateurs. Donc tu ne fais rien de mal si tu en rajoutes trop, en fait.

(*D2)(char);
Le type de déclaration

est complet! Maintenant, nous allons remplacer D2 par la fonction déclarant fonction prenant <parameters> retour , qui est D3(<parameters>) que nous sommes à présent.

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

noter que no les parenthèses sont nécessaires, puisque nous voulons D3 pour être un fonction-déclarant et non un déclarant pointeur cette fois. Super, il ne reste que les paramètres. Le paramètre est fait exactement de la même façon que nous avons fait le type de retour, juste avec char remplacé par void . Alors je vais le copier:

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

j'ai remplacé D2 par ID1 , puisque nous avons terminé avec ce paramètre (c'est déjà un pointeur vers une fonction-pas besoin d'un autre déclarant). ID1 sera le nom du paramètre. Maintenant, je l'ai dit plus haut à la fin, on ajoute le type que tous ces déclarants modifient - celui qui apparaît à la gauche de chaque déclaration. Pour les fonctions, qui devient le type de retour. Pour les pointeurs le pointé à dactylographier etc... C'est intéressant quand écrit le type, il apparaîtra dans l'ordre opposé, à droite :) de toute façon, le substituer donne la déclaration complète. Les deux fois int bien sûr.

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

j'ai appelé l'identificateur de la fonction ID0 dans cet exemple.

Top Down

cela commence par l'identificateur situé à gauche dans la description du type, enveloppant ce déclarant pendant que nous marchons à droite. Commencer par fonction de prise < paramètres > retour

ID0(<parameters>)

la prochaine chose dans la description (Après" retour") était pointeur à . Incorporons-le:

*ID0(<parameters>)

, Puis la prochaine chose était due à la prise de < paramètres > retour "à la 1519550920" . Le paramètre est un char simple, donc nous le remettons tout de suite, car il est vraiment trivial.

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

notez les parenthèses que nous avons ajoutées, puisque nous voulons à nouveau que le * se lie d'abord, et puis le (char) . Sinon, il se lirait fonction de prise < paramètres > fonction de retour ... . Noes, les fonctions retournant des fonctions ne sont même pas autorisées.

Maintenant nous avons juste besoin de mettre < paramètres > . Je vais vous montrer une version courte de la tournure, car je pense que vous avez déjà l'idée de la façon de le faire.

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

vient de mettre int devant les déclarants comme nous l'avons fait avec bottom-up, et nous sommes finis

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

La belle chose

Est bottom-up ou top-down de mieux? J'ai l'habitude de partir 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. Par ailleurs, si vous appliquez tous les opérateurs de cette Déclaration, vous obtiendrez un int:

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

C'est une belle propriété des déclarations en C: la déclaration affirme que si ces opérateurs sont utilisés dans une expression utilisant l'identifiant, alors il donne le type sur la très gauche. C'est comme ça pour les tableaux de trop.

J'espère que vous avez aimé ce petit tutoriel! Maintenant nous pouvons établir un lien avec cela quand les gens s'interrogent sur la syntaxe étrange de déclaration des fonctions. J'ai essayé de mettre aussi peu de C internes que possible. N'hésitez pas à modifier/corriger les choses.

23
répondu Johannes Schaub - litb 2009-05-09 17:38:17
la source

une autre bonne utilisation pour les pointeurs de fonction:

commutation entre les versions sans douleur

ils sont très pratiques à utiliser pour quand vous voulez 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 qui a une console, mais la version finale du logiciel sera mis sur un Avnet ZedBoard (qui a des ports pour les écrans et les consoles, mais ils ne sont pas nécessaires / désirés pour la version finale). Donc pendant le développement, j'utiliserai printf pour voir les messages d'état et d'erreur, mais quand j'aurai fini, je ne veux rien d'imprimé. 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 vais vous définir le 2 prototypes de fonction présente 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;
}

noter comment le pointeur de fonction est prototypé en version.h comme

void (* zprintf)(const char *, ...);

quand il est référencé dans l'application, il commencera à exécuter partout où il pointe, ce qui n'a pas encore été défini.

dans version.c , remarque dans la fonction board_init()zprintf se voit attribuer une fonction unique (dont la fonction correspond à la signature) en fonction de la version qui est définie dans version.h

zprintf = &printf; zprintf appelle printf à des fins de débogage

ou

zprintf = &noprint; zprintf retourne juste et ne lancera 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 printf si en mode de débogage, ou ne rien faire si en mode de publication. C'est beaucoup plus facile que de passer par tout le projet et de commenter ou supprimer du code. Tout ce que je dois faire est de changer la version dans version.h et le code fera le reste!

21
répondu Zack Sheffield 2013-06-11 19:36:22
la source

pointeur de fonction est généralement défini par typedef, et utilisé comme valeur param & return,

Réponses ci-dessus déjà expliqué beaucoup, je donne juste 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;
}
13
répondu Eric Wang 2014-11-10 11:50:57
la source

L'une des grandes utilisations des pointeurs de fonction en C est d'appeler une fonction sélectionnée à l'exécution. Par exemple, la bibliothèque C run-time a deux routines, qsort et bsearch, qui pointent vers une fonction appelée pour comparer deux éléments triés; cela vous permet de trier ou rechercher, respectivement, n'importe quoi, basé sur n'importe quel critère que vous souhaitez utiliser.

un exemple très basique, s'il y a une fonction appelée print (int x, int y) qui à son tour peut nécessiter d'appeler add() de la fonction ou de la sub (), qui sont de types similaires alors ce que nous allons faire, nous allons ajouter un pointeur de fonction en argument à la fonction print() comme indiqué ci-dessous:-

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

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

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

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

    return 0;
}
9
répondu Vamsi 2015-01-03 20:52:29
la source

à partir de zéro fonction a une adresse mémoire à partir de L'endroit où ils commencent à exécuter. En Langage d'Assemblage, Ils Sont appelé (appel "de la fonction de l'adresse de mémoire").Maintenant revenez à C si la fonction a une adresse mémoire alors ils peuvent être manipulés par des pointeurs dans C. Ainsi par les règles de C

1.Vous devez d'abord déclarer un pointeur de fonction 2.Passer L'adresse de la fonction désirée

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

ce simple Programme illustrera tout.

#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");
}

enter image description here après cela permet de voir comment la machine Les comprend.Aperçu de l'enseignement machine du programme ci-dessus en architecture 32 bits.

la zone de la marque Rouge indique comment l'adresse est échangée et stockée dans eax.Alors leur est une instruction d'appel sur eax. eax contient l'adresse désirée de la fonction

3
répondu Mohit Dabas 2014-09-26 12:09:51
la source

Un pointeur de fonction est une variable qui contient l'adresse d'une fonction. Puisque c'est une variable de pointeur bien qu'avec quelques propriétés restreintes, vous pouvez l'utiliser assez beaucoup comme vous le feriez n'importe quelle autre variable de pointeur dans les structures de données.

la seule exception que je puisse imaginer est de traiter le pointeur de fonction comme pointant vers quelque chose d'autre qu'une seule valeur. Faire l'arithmétique des pointeurs en incrémentant ou en décrémentant un pointeur de fonction ou en ajoutant / soustrayant un décalage à un pointeur de fonction n'est pas vraiment d'aucune utilité comme un pointeur de fonction des points que pour une seule chose, le point d'entrée d'une fonction.

la taille d'une variable indicatrice de fonction, le nombre d'octets occupés par la variable, peut varier en fonction de l'architecture sous-jacente, par exemple x32 ou x64 ou autre.

la déclaration pour une variable de pointeur de fonction doit spécifier le même type d'information qu'une déclaration de fonction afin de le compilateur C pour faire le genre de contrôles qu'il fait normalement. 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 y a des cas où ce manque de vérification peut être utile, mais il suffit de se rappeler qu'un filet de sécurité a été retiré.

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 quelque peu similaires en ce que:

  • func est une fonction qui prend un int et un char * et renvoie un int
  • pFunc est un pointeur de fonction à laquelle est attribuée l'adresse d'une fonction qui prend un int et un char * et renvoie un int

ainsi, à partir de ce qui précède, nous pourrions avoir une ligne source dans laquelle l'adresse de la fonction func() est assignée à la fonction pointeur variable pFunc comme dans pFunc = func; .

noter la syntaxe utilisée avec une déclaration/définition de pointeur de fonction dans laquelle les parenthèses sont utilisées pour surmonter les règles de priorité de l'opérateur naturel.

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'Usage 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 le paramètre de longueur variable les listes de définition d'un pointeur de fonction.

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

Ou 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);

C style Casts

vous pouvez utiliser des moulages de style C avec des pointeurs de fonction. Cependant, soyez conscient qu'un compilateur C peut être lax sur les contrôles ou fournir 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 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 déclaration if bien que je ne suis pas sûr à quel point cela serait utile. D'autres opérateurs de comparaison semble 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;
}

Un Tableau de Pointeurs de Fonction

et si vous voulez avoir un tableau de pointeurs de fonction chacun des éléments dont la liste d'arguments a des différences, alors vous pouvez définir un pointeur de fonction avec la liste d'arguments non spécifié (pas void qui signifie pas d'arguments mais juste non spécifié) quelque chose comme ce qui suit bien que vous puissiez voir des avertissements du compilateur C. Cela fonctionne aussi pour un paramètre de pointeur de fonction vers 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.
}

C style namespace utilisant Global struct avec des pointeurs de fonction

vous pouvez utiliser le mot-clé static pour spécifier une fonction dont le nom est file scope et ensuite l'attribuer à une variable globale comme un moyen de fournir quelque chose de similaire à la fonctionnalité namespace de C++.

Dans un fichier d'en-tête de définir 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 c source:

#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 globale struct et le nom du membre pour accéder à la fonction. Le modificateur const 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 pourrait faire quelque chose de similaire au style C namespace approche dans laquelle une interface de bibliothèque particulière est demandée à une méthode d'usine dans une interface de bibliothèque qui soutient la création d'un struct contenant des pointeurs de fonction.. Cette interface de bibliothèque charge la version DLL demandée, crée une structure avec les pointeurs de fonction nécessaires, puis renvoie la structure à l'appelant requérant pour utilisation.

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 ceci pourrait être utilisé comme dans:

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

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

pointeurs de fonction pour créer des Délégués, des gestionnaires et des Callbacks

vous pouvez utiliser des pointeurs de fonction comme un moyen de 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 de bibliothèque Standard C qsort() et bsearch() pour fournir le classement de l'ordre de tri d'une liste d'éléments ou d'effectuer une recherche binaire sur une liste triée des éléments. La fonction de comparaison delegate spécifie l'algorithme de collation utilisé dans le tri ou la recherche binaire.

une autre utilisation est similaire à l'application d'un algorithme à un conteneur de bibliothèque standard C++.

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 est avec le code source GUI dans lequel un gestionnaire pour un un événement particulier est enregistré en fournissant un pointeur de fonction qui est effectivement appelé lorsque l'événement se produit. Le cadre Microsoft MFC avec ses maps de message utilise quelque chose de similaire pour gérer les messages Windows qui sont livrés à une fenêtre ou un thread.

les fonctions asynchrones qui nécessitent un rappel sont similaires à celles d'un gestionnaire d'événements. L'utilisateur de la fonction asynchrone appelle la fonction asynchrone pour lancer une action et fournit un pointeur de fonction qui la fonction asynchrone appellera une fois l'action terminée. Dans ce cas, l'événement est la fonction asynchrone accomplissant sa tâche.

2
répondu Richard Chambers 2018-08-20 08:47:15
la source

puisque les pointeurs de fonction sont souvent des callbacks dactylographiés, vous pourriez vouloir jeter un oeil à type safe callbacks . La même chose s'applique aux points d'entrée, etc des fonctions qui ne sont pas des callbacks.

C est assez volage et de pardonner en même temps :)

0
répondu Tim Post 2009-05-09 17:56:12
la source
Les pointeurs de fonction

sont utiles dans de nombreuses situations, par exemple:

  • objets COM membres sont le pointeur de fonction ag: This->lpVtbl->AddRef(This); AddRef est un pointeur vers une fonction.
  • fonction de rappel, par exemple une fonction définie par l'utilisateur pour comparer deux variables à transmettre comme un rappel à une fonction de tri.
  • très utile pour la mise en œuvre de plugin et SDK d'application.
-8
répondu milevyo 2016-01-02 16:15:05
la source

Autres questions sur c function-pointers