Comment fonctionnent les pointeurs de fonction en C?
J'ai eu une certaine expérience dernièrement avec des pointeurs de fonction dans C.
Donc, en continuant avec la tradition de répondre à vos propres questions, j'ai décidé de faire un petit résumé des bases mêmes, pour ceux qui ont besoin d'une plongée rapide dans le sujet.
12 réponses
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, nous allons définir un pointeur vers une fonction qui reçoit 2 int
s et renvoie un int
:
int (*functionPtr)(int,int);
Maintenant, nous pouvons pointer en toute sécurité vers notre fonction:
functionPtr = &addInt;
Maintenant que nous avons un pointeur vers la fonction, utilisons-le:
int sum = (*functionPtr)(2, 3); // sum == 5
Passer le pointeur vers une autre fonction est fondamentalement le même:
int add2to3(int (*functionPtr)(int, int)) {
return (*functionPtr)(2, 3);
}
Nous pouvons utiliser la fonction pointeurs dans les valeurs de retour aussi (essayez de suivre, ça devient salissant):
// 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;
}
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éfinissons le texte d'une classe String
sur "hello"
.
À l'aide des pointeurs de fonction, , il est possible d'émuler des méthodes en C.
Comment cela se fait-il?
Le String
la classe 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 la fonction déclarée. Lors de la préparation de l'instance de String
, la fonction newString
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 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ée est qu'il n'y a pas de concept d'instance d'un objet et d'avoir des méthodes qui font réellement partie d'un objet, donc un "self object" doit être transmis à chaque invocation. (Et le internal
est juste un struct
caché qui a été omis de la liste de code plus tôt - c'est un moyen d'effectuer le masquage d'informations, mais cela n'est pas pertinent pour les pointeurs de fonction.)
Donc, plutôt que être en mesure de le faire s1->set("hello");
, il faut passer dans l'objet pour effectuer l'action sur s1->set(s1, "hello")
.
Avec cette explication mineure devant passer dans une référence à vous-même, nous allons passer à la partie suivante, qui est héritage en C.
Disons que nous voulons faire une sous-classe de String
, disons qu'un ImmutableString
. Afin de rendre la chaîne immuable, la méthode set
ne sera pas accessible, tout en conservant 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 à nouveau des pointeurs de fonction. Cette fois, la déclaration pour la méthode set
n'est pas présente, par conséquent, elle ne peut pas être appelée dans un ImmutableString
.
Quant à l'implémentation 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 vers les méthodes get
et length
se réfèrent en fait à la méthode String.get
et String.length
, par en passant par la variable base
qui est un objet String
stocké en interne.
L'utilisation d'un pointeur de fonction peut atteindre l'héritage d'une méthode à partir d'une superclasse.
Nous pouvons continuer àpolymorphisme en C .
, par exemple, Si nous voulions changer le comportement de la length
méthode pour retourner 0
tout le temps dans le ImmutableString
classe pour une raison quelconque, tout ce qui devrait être fait est de:
- Ajouter une fonction qui va servir la méthode de substitution
length
. - allez dans le "constructeur" et définissez le pointeur de fonction sur la méthode de substitution
length
.
L'ajout d'une méthode de substitution length
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 connecté 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 les length
méthode ImmutableString
classe que le String
classe, maintenant la length
méthode fera référence au comportement défini dans la fonction lengthOverrideMethod
.
Je dois ajouter un avertissement que j'apprends encore à é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 en termes de meilleure façon d'implémenter la POO en C. Mais mon but était d'essayer d'illustrer l'une des nombreuses utilisations
Pour plus d'informations sur la façon d'effectuer une programmation orientée objet en C, veuillez vous référer à la questions suivantes:
Le guide pour se faire virer: comment abuser des pointeurs de fonction dans GCC sur les machines x86 en compilant votre code à la main:
Ces littéraux de chaîne sont des octets de code machine x86 32 bits. 0xC3
est une instruction x86 ret
.
Normalement, vous ne les écririez pas à la main, vous écririez en langage assembleur, puis utilisez un assembleur comme nasm
pour l'assembler en un binaire plat que vous hexdumpez en un littéral de chaîne C.
-
Renvoie la valeur actuelle sur le registre EAX
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
-
É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);
-
Écrivez un compteur for-loop à 1000, en 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
-
Vous pouvez même écrire une fonction récursive qui compte à 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 des littéraux de chaîne dans la section .rodata
(ou .rdata
sous Windows), qui est liée dans le cadre du segment de texte (avec le code des fonctions).
Le segment de texte a Read + exec permission, donc lancer des littéraux de chaîne pour les pointeurs de fonction fonctionne sans avoir besoin d'appels système mprotect()
ou VirtualProtect()
comme vous auriez besoin de mémoire allouée dynamiquement. (Ou gcc -z execstack
lie le programme avec stack + data segment + heap executable, comme un hack rapide.)
Pour les démonter, vous pouvez compiler ceci 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'assemblage, et découvrir que ce code viole L'ABI en écrasant EBX (un registre préservé des appels) 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 sous Windows, Linux, OS X, etc.: les conventions d'appel par défaut sur tous ces systèmes d'exploitation transmettent des arguments sur la pile au lieu de plus efficacement dans les registres. Mais EBX est conservé dans toutes les conventions d'appel normales, donc l'utiliser comme un registre à gratter sans l'enregistrer / le restaurer peut facilement faire planter l'appelant.
L'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);
}
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 de - fonction:
D(<parameters>)
: d fonction prenant<
paramètres>
retour
Alors que D est un autre déclarateur 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. Nous allons essayer 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 char et 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 en utilisant typedefs. Sans typedefs, ce n'est pas difficile non plus avec les règles déclarator ci-dessus, appliquées de manière cohérente. Comme vous le voyez, j'ai manqué la partie vers laquelle le pointeur pointe, et la chose que la fonction renvoie. C'est ce qui apparaît tout à 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. Construire de manière cohérente, d'abord verbeux - 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 le voyez, on peut décrire complètement un type en ajoutant les déclarateurs les uns après les autres. La Construction peut se faire de deux façons. L'un est de bas en haut, en commençant par la bonne chose (feuilles) et en travaillant jusqu'à l'identifiant. L'autre façon est de haut en bas, en commençant par l'identifiant, en descendant jusqu'aux feuilles. Je vais montrer les deux sens.
Ascendant
La Construction commence par la chose à droite: la chose retournée, qui est la fonction prenant char. Pour garder les déclarateurs distincts, je vais les numéroter:
D1(char);
Inséré le paramètre char directement, car il est trivial. Ajouter un pointeur au déclarateur en remplaçant D1
par *D2
. Notez que nous devons envelopper les parenthèses autour de *D2
. Ce qui peut être connu par recherche de la priorité de l'opérateur *-operator
et de l'opérateur d'appel de fonction ()
. Sans nos parenthèses, le compilateur le lirait comme *(D2(char p))
. Mais ce ne serait plus un simple remplacement de D1 par *D2
, bien sûr. Les parenthèses sont toujours autorisées autour des déclarateurs. Donc, vous ne faites rien de mal si vous ajoutez trop d'entre eux, en fait.
(*D2)(char);
Le type de retour est complet! Maintenant, remplacons D2
par le déclarateur de fonction fonction prenant <parameters>
retour , qui est D3(<parameters>)
qui nous sommes à maintenant.
(*D3(<parameters>))(char)
Notez qu'aucune parenthèse n'est nécessaire, puisque nous voulons D3
être un déclarateur de fonction et non un déclarateur de pointeur cette fois. Génial, la seule chose qui reste est les paramètres pour cela. Le paramètre est fait exactement de la même manière que nous avons fait le type de retour, juste avec char
remplacé par void
. Je vais donc 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 de un autre déclarateur). ID1
sera le nom du paramètre. Maintenant, j'ai dit ci - dessus à la fin on ajoute le type que tous ces déclarateurs modifient-celui qui apparaît tout à gauche de chaque déclaration. Pour les fonctions, cela devient le type de retour. Pour les pointeurs, le pointé vers le type etc... C'est intéressant quand on écrit le type, il apparaîtra dans l'ordre opposé, tout à droite :) de toute façon, le remplacer 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.
De Haut En Bas
Cela commence à l'identifiant tout à gauche dans la description du type, enveloppant ce déclarateur lorsque nous nous frayons un chemin à travers la droite. Commencez avec fonction prenant <
paramètres>
retourner
ID0(<parameters>)
La prochaine chose dans la description (Après "retour") était pointeur sur. Intégrons-le:
*ID0(<parameters>)
Puis le suivant la chose était functon prenant <
paramètres >
retour . Le paramètre est un caractère simple, donc nous le remettons tout de suite, car c'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 lirait fonction prenant <
paramètres >
fonction de retour .... Non, les fonctions renvoyant des fonctions ne sont même pas autorisées.
Maintenant nous avons juste besoin de mettre <
paramètres>
. Je vais montrer une version courte de la dérision, puisque je pense que vous avez déjà maintenant l'idée de comment le faire.
pointer to: *ID1
... function taking void returning: (*ID1)(void)
Il suffit de mettre int
avant les déclarateurs comme nous l'avons fait avec bottom-up, et nous sommes finis
int (*ID0(int (*ID1)(void)))(char)
La belle chose
Est-ce que le bottom-up ou le top-down est meilleur? Je suis habitué à bottom-up, mais certaines personnes peuvent être plus à l'aise avec top-down. C'est une question de goût je pense. Incidemment, si vous appliquez tous les opérateurs dans cette déclaration, vous finirez par obtenir 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 en utilisant l'identifiant, alors elle donne le type tout à gauche. C'est comme ça pour les tableaux aussi.
J'espère que vous avez aimé ce petit tutoriel! Maintenant, nous pouvons lier à cela quand les gens s'interrogent sur la syntaxe de déclaration étrange des fonctions. J'ai essayé de mettre aussi peu de C internes que possible. N'hésitez pas à éditer / réparer les choses.
Un autre bon usage pour les pointeurs de fonction:
commutation entre les versions sans douleur
Ils sont très pratiques à utiliser lorsque 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 mise sur un Zedboard Avnet (qui a des ports pour les écrans et les consoles, mais ils ne sont pas nécessaires/recherchés pour la version finale). Donc, au cours de développement, je vais utiliser printf
pour afficher les messages d'état et d'erreur, mais quand j'ai terminé, je ne veux 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 vais définir 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 version.h
comme
void (* zprintf)(const char *, ...);
Quand il est référencé dans l'application, il commencera à s'exécuter partout où il pointe, ce qui a encore pour être défini.
Dans version.c
, notez dans la fonction board_init()
où zprintf
se voit attribuer une fonction unique (dont la signature de fonction correspond) en fonction de la version définie dans version.h
zprintf = &printf;
zprintf appelle printf à des fins de débogage
Ou
zprintf = &noprint;
zprintf retourne juste 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 printf
si en mode débogage, ou rien si en mode libération. C'est beaucoup plus facile que de passer par l'ensemble du projet et de commenter ou de supprimer du code. Tout ce que je dois faire est de changer la version dans version.h
et le code fera le reste!
Le pointeur de fonction est généralement défini par typedef, et utilisé comme param et valeur de retour,
Les réponses ci-dessus ont déjà beaucoup expliqué, 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 ∑
}
// test - use function pointer as variable,
void test_pointer_as_variable() {
// create a pointer to function,
two_num_operation sum_p = ∑
// 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 a deux routines, qsort et bsearch, qui prennent un pointeur vers une fonction appelée pour comparer deux éléments triés; cela vous permet de trier ou de rechercher, respectivement, n'importe quoi, en fonction des critères 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 la fonction add() ou sub() qui sont de types similaires alors ce que nous ferons, nous ajouterons un argument de pointeur de fonction à 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;
}
La fonction à partir de zéro a une adresse mémoire à partir de laquelle ils commencent à s'exécuter. En langage Assembleur, ils sont appelés comme (appel "adresse mémoire de la fonction").Maintenant revenez à C si la fonction a une adresse mémoire alors ils peuvent être manipulés par des pointeurs dans C.So par les règles de C
1.Vous devez d'abord déclarer un pointeur de fonction 2.Passez l'adresse de la fonction souhaitée
****Note->les fonctions doivent être de même type****
Ce Programme Simple Illustrer 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");
}
Après cela permet de voir comment la machine Les comprend.Aperçu de l'instruction machine du programme ci-dessus en architecture 32 bits.
La zone de marque rouge montre 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
Une fonction pointeur est une variable qui contient l'adresse d'une fonction. Comme il s'agit d'une variable de pointeur avec des 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 pointant vers autre chose qu'une seule valeur. 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 à une fonction le pointeur n'est pas vraiment d'utilité en tant que pointeur de fonction ne pointe que vers 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, peut varier en fonction de l'architecture sous-jacente, par exemple x32 ou x64 ou autre.
La déclaration d'une variable de pointeur de fonction doit spécifier le même type d'information qu'une déclaration de fonction pour que le compilateur C effectue les types de vérifications qu'il le 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 rappelez-vous juste 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éclararations sont quelque peu similaires en ce que:
-
func
est une fonction qui prend unint
et achar *
et renvoie unint
-
pFunc
est un pointeur de fonction à laquelle est attribuée l'adresse d'une fonction qui prend unint
et achar *
et renvoie unint
Ainsi, à partir de ce qui précède, nous pourrions avoir une ligne source dans laquelle l'adresse de la fonction func()
est affectée à la variable de pointeur de fonction pFunc
comme dans pFunc = func;
.
Notez la syntaxe utilisée avec une déclaration/définition de pointeur de fonction dans laquelle les parenthèses sont utilisées pour surmonter la priorité de l'opérateur naturel règle.
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 De
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, ...);
Ou vous ne pouvez pas spécifier une liste de paramètres du tout. Cela peut être utile mais cela é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 jette
Vous pouvez utiliser C style jette avec des pointeurs de fonction. Cependant, sachez qu'un compilateur C peut être laxiste sur les vérifications 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 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 if
bien que je ne sache pas à quel point cela serait utile. D'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;
}
Un Tableau de Fonction Les pointeurs
Et si vous voulez avoir un tableau de pointeurs de fonction chacun des éléments dont la liste d'arguments a des différences, vous pouvez définir un pointeur de fonction avec la liste d'arguments non spécifiée (pas void
ce qui signifie pas d'arguments mais juste non spécifié) quelque chose comme ce qui suit bien que vous puissiez voir des avertissements Cela fonctionne également pour un paramètre de pointeur de fonction à 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
en utilisant Global struct
avec fonction Les pointeurs
Vous pouvez utiliser le mot clé static
pour spécifier une fonction dont le nom est file scope, puis l'attribuer à une variable globale afin 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 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};
Cela serait ensuite utilisé en spécifiant le nom complet de la variable struct globale et nom du membre pour accéder à la fonction. Le modificateur const
est utilisé sur le global afin 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 à l'approche C style namespace
dans laquelle une interface de bibliothèque particulière est demandée à partir d'une méthode factory dans une interface de bibliothèque qui prend en charge la création d'un struct
contenant des pointeurs de fonction.. Cette interface de bibliothèque charge le la version DLL demandée, crée une structure avec les pointeurs de fonction nécessaires, puis renvoie la structure à l'appelant demandeur 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 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 le code qui utilise un modèle particulier du matériel sous-jacent. Les pointeurs de fonction sont remplis avec des fonctions spécifiques au matériel par une usine pour fournir la fonctionnalité spécifique au matériel qui implémente des fonctions spécifié dans le modèle matériel abstrait. Cela peut être utilisé pour fournir une couche matérielle abstraite utilisée par un logiciel qui appelle une fonction d'usine afin d'obtenir l'interface de fonction matérielle spécifique, puis utilise les pointeurs de fonction fournis pour effectuer des actions pour le matériel sous-jacent sans avoir besoin de connaître les détails d'implémentation sur la cible spécifique.
Pointeurs de fonction pour créer des Délégués, des gestionnaires et des rappels
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 C Standard qsort()
et bsearch()
pour fournir l'ordre de classement pour trier une liste d'éléments ou effectuer une recherche binaire sur une liste triée d'éléments. 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 à une bibliothèque de modèles Standard C++ conteneur.
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 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. Le framework Microsoft MFC avec ses cartes de messages 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 à un gestionnaire d'événements. L'utilisateur de la fonction asynchrone appelle le 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 complétant sa tâche.
Étant donné que les pointeurs de fonction sont souvent des callbacks typés, vous pouvez 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 rappels.
C est assez inconstant et indulgent en même temps:)
Pointeurs de Fonction sont utiles dans de nombreuses situations, par exemple:
- les membres des objets COM pointent vers la fonction ag:
This->lpVtbl->AddRef(This);
AddRef est un pointeur vers une fonction. - fonction callback, par exemple une fonction définie par l'utilisateur à compare deux variables à transmettre comme un rappel à une fonction de tri.
- très utile pour l'implémentation du plugin et le SDK d'application.