J'ai eu récemment de l'expérience avec les pointeurs de fonction en C.
Donc, 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.
J'ai eu récemment de l'expérience avec les pointeurs de fonction en C.
Donc, 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.
Réponses:
Commençons par une fonction de base sur laquelle nous allons pointer :
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 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 sur la fonction, utilisons-le:
int sum = (*functionPtr)(2, 3); // sum == 5
Passer le pointeur à une autre fonction est fondamentalement le même:
int add2to3(int (*functionPtr)(int, int)) {
return (*functionPtr)(2, 3);
}
Nous pouvons également utiliser des 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 c'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;
}
pshufb
, c'est lent, donc l'implémentation antérieure est toujours plus rapide. x264 / x265 l'utilisent largement et sont open source.
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 le manque d'un new
opérateur est un abandon mort, mais cela semble impliquer que nous définissons le texte d'une String
classe "hello"
.
En utilisant des pointeurs de fonction, il est possible d'imiter les méthodes en C .
Comment est-ce accompli?
La String
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 String
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 voir, les méthodes de la String
classe sont en fait des pointeurs de fonction vers la fonction déclarée. Lors de la préparation de l'instance de String
, la newString
fonction 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 getString
fonction appelée en appelant la get
méthode est définie comme suit:
char* getString(const void* self_obj)
{
return ((String)self_obj)->internal->value;
}
Une chose qui peut être remarquée, c'est qu'il n'y a pas de concept d'instance d'un objet et d'avoir des méthodes qui sont en fait une partie d'un objet, donc un "self object" doit être transmis à chaque invocation. (Et internal
c'est juste un caché struct
qui a été omis de la liste de code plus tôt - c'est un moyen de masquer des informations, mais cela n'est pas pertinent pour les pointeurs de fonction.)
Donc, plutôt que de pouvoir le faire s1->set("hello");
, il faut passer l'objet pour effectuer l'action s1->set(s1, "hello")
.
Avec cette explication mineure avoir à passer dans une référence à vous - même de la route, nous allons passer à la partie suivante, qui est l' héritage en C .
Disons que nous voulons créer une sous-classe de String
, disons an ImmutableString
. Afin de rendre la chaîne immuable, la set
méthode ne sera pas accessible, tout en maintenant l'accès à get
et length
, et en forçant le "constructeur" à accepter a 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 de la set
méthode n'est pas présente, par conséquent, elle ne peut pas être appelée dans a ImmutableString
.
Quant à l'implémentation de la ImmutableString
, le seul code pertinent est la fonction "constructeur", la 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
, la fonction pointe vers les méthodes get
et length
fait référence à la méthode String.get
et String.length
, en parcourant la base
variable qui est un String
objet stocké en interne .
L'utilisation d'un pointeur de fonction peut permettre l'héritage d'une méthode d'une superclasse.
Nous pouvons encore continuer à polymorphisme C .
Si par exemple nous voulions changer le comportement de la length
méthode pour retourner 0
tout le temps dans la ImmutableString
classe pour une raison quelconque, tout ce qu'il faudrait faire est de:
length
méthode prioritaire .length
méthode prioritaire .L'ajout d'une length
méthode prioritaire dans ImmutableString
peut être effectué en ajoutant un lengthOverrideMethod
:
int lengthOverrideMethod(const void* self)
{
return 0;
}
Ensuite, le pointeur de fonction pour la length
méthode 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 la length
méthode en ImmutableString
classe comme la String
classe, maintenant la length
méthode se référera au comportement défini dans la lengthOverrideMethod
fonction.
Je dois ajouter une clause de non-responsabilité que j'apprends toujours à é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 qui peuvent simplement être hors de propos 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 des pointeurs de fonction.
Pour plus d'informations sur la façon d'effectuer une programmation orientée objet en C, veuillez vous référer aux questions suivantes:
ClassName_methodName
convention de dénomination des fonctions. Ce n'est qu'alors que vous obtenez les mêmes coûts d'exécution et de stockage qu'en C ++ et Pascal.
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 ret
instruction x86 .
Normalement, vous ne les écrivez pas à la main, vous écrivez en langage assembleur, puis vous utilisez un assembleur comme nasm
pour l'assembler dans un binaire plat que vous hexdumpez dans un littéral de chaîne C.
Renvoie la valeur actuelle dans le registre EAX
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
Écrire une fonction de swap
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);
Ecrire un compteur de boucles à 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
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);
Notez que les compilateurs placent des littéraux de chaîne dans la .rodata
section (ou .rdata
sous Windows), qui est liée en tant que partie du segment de texte (avec le code des fonctions).
Le segment de texte a lu + l' autorisation Exec, donc coulée littéraux de chaîne pour des pointeurs de fonction fonctionne sans avoir besoin 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 pile + segment de données + 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 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 viole l'ABI en encombrant EBX (un registre préservé par les 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, et ainsi de suite: les conventions d'appel par défaut sur tous ces OS passent des arguments sur la pile au lieu d'être plus efficacement dans les registres. Mais EBX est préservé dans toutes les conventions d'appel normales, donc l'utiliser comme registre de travail sans l'enregistrer / le restaurer peut facilement faire planter l'appelant.
Une de mes utilisations préférées pour les pointeurs de fonction est comme itérateurs bon marché et faciles -
#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);
}
int (*cb)(void *arg, ...)
. La valeur de retour de l'itérateur me permet également de m'arrêter tôt (si différent de zéro).
Les pointeurs de fonction deviennent faciles à déclarer une fois que vous disposez des déclarants de base:
ID
: ID est*D
: pointeur DD(<parameters>)
: fonction D prise <
paramètres de >
retourAlors que D est un autre déclarant construit en utilisant ces mêmes règles. En fin de compte, 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 prenant un pointeur vers une fonction ne prenant rien et retournant int, et renvoyant un pointeur vers une fonction prenant un char et retournant int. Avec les définitions de type, 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 raté la partie vers laquelle pointe le pointeur et la chose que la fonction retourne. C'est ce qui apparaît à l'extrême gauche de la déclaration, et ce n'est pas intéressant: c'est ajouté à la fin si on a déjà construit le déclarant. Faisons cela. Construire de manière cohérente, premier mot - 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 un type complètement en ajoutant des déclarants les uns après les autres. La construction peut se faire de deux manières. La première est ascendante, en commençant par la bonne chose (les feuilles) et en progressant 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.
La construction commence par la chose à droite: la chose retournée, qui est la fonction qui prend le caractère. Pour garder les déclarateurs distincts, je vais les numéroter:
D1(char);
Inséré directement le paramètre char, car il est trivial. Ajout d'un pointeur sur le déclarant en le remplaçant D1
par *D2
. Notez que nous devons entourer les parenthèses *D2
. Cela peut être connu en recherchant la priorité de l' *-operator
opérateur d'appel de fonction ()
. Sans nos parenthèses, le compilateur le lirait *(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éclarants. Donc, vous ne faites rien de mal si vous en ajoutez trop, en fait.
(*D2)(char);
Le type de retour est terminé! Maintenant, remplaçons D2
par la fonction déclarant la fonction prenant <parameters>
return , qui est D3(<parameters>)
ce que nous sommes maintenant.
(*D3(<parameters>))(char)
Notez qu'aucune parenthèse n'est nécessaire, car nous voulons D3
être un déclarant de fonction et non un déclarant de pointeur cette fois. Génial, il ne reste que 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
, car 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, j'ai dit ci-dessus à la fin, on ajoute le type que tous ces déclarants modifient - celui qui apparaît à l'extrême gauche de chaque déclaration. Pour les fonctions, cela devient le type de retour. Pour les pointeurs du type pointé etc ... C'est intéressant quand on écrit le type, il apparaîtra dans l'ordre inverse, tout à droite :) Quoi qu'il en soit, 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'identifiant de la fonction ID0
dans cet exemple.
Cela commence à l'identifiant tout à gauche dans la description du type, enveloppant ce déclarant lorsque nous nous dirigeons vers la droite. Commencez avec la fonction de prise de <
paramètres >
renvoyant
ID0(<parameters>)
La prochaine chose dans la description (après "retour") était le pointeur sur . Incorporons-le:
*ID0(<parameters>)
Alors la chose suivante était functon prendre des <
paramètres de >
retour . Le paramètre est un simple caractère, nous le mettons donc à nouveau immédiatement, car il est vraiment trivial.
(*ID0(<parameters>))(char)
Notez les parenthèses que nous avons ajoutées, car nous voulons à nouveau que les *
liaisons soient d'abord, puis les (char)
. Dans le cas contraire , on lirait la fonction prise <
paramètres de >
retour fonction ... . Non, les fonctions renvoyant des fonctions ne sont même pas autorisées.
Maintenant, nous avons juste besoin de mettre des <
paramètres >
. Je vais montrer une version courte de la dérivation, car je pense que vous avez déjà l'idée de le faire.
pointer to: *ID1
... function taking void returning: (*ID1)(void)
Mettez juste int
devant les déclarants comme nous l'avons fait de bas en haut, et nous avons terminé
int (*ID0(int (*ID1)(void)))(char)
La méthode ascendante ou descendante est-elle meilleure? J'ai l'habitude 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 dans cette déclaration, vous finirez par obtenir un int:
int v = (*ID0(some_function_pointer))(some_char);
C'est une belle propriété de déclarations en C: La déclaration affirme que si ces opérateurs sont utilisés dans une expression 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 établir un lien vers cela lorsque les gens s'interrogent sur l'étrange syntaxe de déclaration des fonctions. J'ai essayé de mettre le moins de C internes possible. N'hésitez pas à éditer / corriger des choses dedans.
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 (qui possède des ports pour les écrans et les consoles, mais ils ne sont pas nécessaires / recherchés pour le version finale). Ainsi, pendant le développement, je vais utiliser printf
pour afficher l'état et les messages d'erreur, mais quand j'ai terminé, je ne veux rien imprimer. Voici ce que j'ai fait:
// 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 la fonction 2 prototypes présents dansversion.h
#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;
}
Remarquez comment le pointeur de fonction est prototypé en version.h
tant que
void (* zprintf)(const char *, ...);
Lorsqu'il est référencé dans l'application, il commencera à s'exécuter là où il pointe, ce qui n'a pas encore été défini.
Dans version.c
, notez dans la board_init()
fonction à laquelle zprintf
est affectée une fonction unique (dont la signature de fonction correspond) en fonction de la version définie dansversion.h
zprintf = &printf;
zprintf appelle printf à des fins de débogage
ou
zprintf = &noprint;
zprintf retourne simplement et n'exécutera pas de code inutile
L'exécution du code ressemblera à ceci:
#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
s'il est en mode débogage, ou ne fera rien s'il est en mode release. C'est beaucoup plus facile que de parcourir l'intégralité du projet et de commenter ou de supprimer du code. Tout ce que je dois faire est de changer la version version.h
et le code fera le reste!
Le pointeur de fonction est généralement défini par typedef
et utilisé comme paramètre 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 qui est appelée pour comparer deux éléments en cours de tri; 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 une fonction (soit add()
ou sub()
, qui sont du même type) alors ce que nous ferons, nous ajouterons 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;
}
La sortie est:
la valeur est: 410 la
valeur est: 390
La fonction de démarrage à partir de zéro a une adresse mémoire à partir de l'endroit où ils commencent à s'exécuter. Dans le langage d'assemblage, ils sont appelés comme (appelez "l'adresse mémoire de la fonction"). Revenez maintenant à C Si la fonction a une adresse mémoire, ils peuvent être manipulés par des pointeurs en C. Donc, selon les règles de C
1.Tout d'abord, vous devez déclarer un pointeur sur la fonction 2.Passez 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");
}
Après cela, voyons comment la machine les comprend.Glimpse des instructions machine du programme ci-dessus dans une 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.
Un pointeur de fonction est une variable qui contient l'adresse d'une fonction. Comme il s'agit d'une variable de pointeur mais avec des propriétés restreintes, vous pouvez l'utiliser à peu près comme vous le feriez pour 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 à 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 exemple x32 ou x64 ou autre.
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 effectue les types de vérifications qu'il effectue 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 contrôle 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 quelque peu similaires en ce sens:
func
est une fonction qui prend un int
et un char *
et renvoie unint
pFunc
est un pointeur de fonction auquel est affectée l'adresse d'une fonction qui prend un int
et un char *
et renvoie unint
Donc, à 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 des parenthèses sont utilisées pour surmonter les 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, ...);
Ou vous ne pouvez pas du tout spécifier une liste de paramètres. Cela peut être utile, mais il é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);
Moulages de style C
Vous pouvez utiliser des modèles de style C 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 le pointeur de fonction à l'égalité
Vous pouvez vérifier qu'un pointeur de fonction est égal à une adresse de fonction particulière à l'aide d'une if
instruction, mais je ne sais 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 pointeurs de fonction
Et si vous voulez avoir un tableau de pointeurs de fonction pour 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
qui ne signifie pas d'arguments mais simplement non spécifiée) quelque chose comme le suivant bien que vous peut voir des avertissements du compilateur C. Cela fonctionne également 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.
}
Style C namespace
Utilisation de Global struct
avec des pointeurs de fonction
Vous pouvez utiliser le static
mot - clé pour spécifier une fonction dont le nom est la portée du fichier, puis l'attribuer à une variable globale afin de fournir quelque chose de similaire à la namespace
fonctionnalité de C ++.
Dans un fichier d'en-tête, définissez une structure qui sera notre espace de noms ainsi qu'une variable globale qui l'utilisera.
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 de structure globale et le nom du membre pour accéder à la fonction. Le const
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 pourrait faire quelque chose de similaire à l' namespace
approche de style C dans laquelle une interface de bibliothèque particulière est demandée à partir d'une méthode d'usine dans une interface de bibliothèque qui prend en charge la création d'une struct
fonction contenant des pointeurs. 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 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 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 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 de 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 pour déléguer une tâche ou une fonctionnalité. L'exemple classique en C est le pointeur de fonction 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 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 de bibliothèque de modèles 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 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. Le cadre Microsoft MFC avec ses mappages de messages utilise quelque chose de similaire pour gérer les messages Windows qui sont remis à 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 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 qui termine sa tâche.
Étant donné que les pointeurs de fonction sont souvent des rappels typés, vous souhaiterez peut-être consulter les rappels sécurisés de type . Il en va de même pour les points d'entrée, etc. des fonctions qui ne sont pas des rappels.
C est assez volage et pardonne en même temps :)