Pointeur de fonction en C

Le pointeur de fonction en C permet de fournir la liaison d’exécution d’une fonction.
En réalité, le pointeur de fonction est parfois mal aimé et pas assez souvent utilisé. Cependant, il s’agit d’un outil très pratique pour ajouter du polymorphisme ou pour simplifier certaines structures complexes.

Pointeur de fonction en C

Généralités

Déclaration

La déclaration d’un pointeur de fonction en C est presque similaire à celle d’une fonction. Il faut écrire le type de retour, la liste des arguments et le nom du pointeur de fonction.

Function_return_type (*Function_Pointer_name)(Function argument list);

Exemples :

//It can point to function which takes an int as an argument and return nothing.
void (*fpData)(int);

//It can point to function which takes a const char * as an argument and return nothing.
void (*pfDisplayMessage)(const char *);

//It can point to function which takes an int as an argument and return int.
int  (*fpData)(int);

Initialisation

Un pointeur de fonction en C est similaire à un pointeur normal, il est nécessaire de l’initialiser. Néanmoins, il faut bien respecter la signature du pointeur. En effet la fonction pointée doit avoir le même type de retour et le même type d’argument.

//declaration of function pointer
int (* pfAddTwoNumber) (int, int);
//function with same signature
int AddTwoNumber(int a, int b)
{
    return (a + b);
}

Il y a deux syntaxes possibles pour initialiser un pointeur de fonction :

pfAddTwoNumber = &AddTwoNumber;
    or
pfAddTwoNumber = AddTwoNumber;

De même, il est possible d’initialiser un pointeur de fonction en même temps que sa déclaration :

int (* pfAddTwoNumber) (int, int) = AddTwoNumber;

Attention, un pointeur de fonction doit être toujours initialisé. Dans le cas contraire, l’appel d’un pointeur de fonction amène à un plantage de l’application ou du système.
Il est conseillé de l’initialiser à NULL au moment de sa création et de vérifier la validité d’un pointeur de fonction avant de l’utiliser.

void (*fpData)(int) = NULL;

int program(int data)
{
    if( fpData != NULL)
    {
        (*fpData)(data); 
        return Valid;
    }
    else
    {
        return Invalid;
    }
}

Appel d’une fonction à l’aide du pointeur de fonction

Une fonction utilisant un pointeur de fonction s’appelle de la manière suivante :

//Function pointer which has argument list
(*Function_Name)(ArgumentList);
    or
//Function pointer without argument list
(*Function_Name)();

Il est possible d’omettre l’opérateur d’indirection au moment de l’appel de la fonction en utilisant le pointeur de fonction :

//Calling the function using the function pointer
iRetValue = (*pfAddTwoNumber)(10,20);
        or
//Calling the function using the function pointer        
iRetValue = pfAddTwoNumber(10,20);

Utilisation de typedef avec le pointeur de fonction

En utilisant typedef, nous pouvons rendre la déclaration du pointeur de fonction plus facile et plus lisible.
En effet, l’utilisation d’un typedef pour un pointeur est très utile lors de la création d’un tableau de pointeurs de fonctions. De plus, il est également très utile pour utiliser un pointeur de fonction en argument ou en retour de fonction.

Exemple :

typedef  int (*pfunctPtr)(int, int); /* function pointer */

//Function pointer declaration
pfunctPtr pSomeFunction = NULL;

Exemple d’un tableau de pointeur de fonction :

//typedef of array of function pointers
typedef int (*apfArithmatics[3])(int,int);

apfArithmatics aArithmaticOperation = { AddTwoNumber,SubTwoNumber,MulTwoNumber };

De surcroît, un typedef peut être utilisé pour faire un cast :

void *pvHandle = NULL;

typedef int (*pf)(int);
pf JumptoApp  =  (pf)pvHandle;

Pointeur de fonction comme argument

Il est possible de passer un pointeur de fonction comme argument.
Ainsi la même fonction peut faire usage de fonctions différentes en utilisant le pointeur de fonction.

Exemple :

typedef  int (*pfunctPtr)(int, int); /* function pointer */

//function pointer as arguments
int functionWithPointerArgument(int iData1,int iData2, pfunctPtr functPtr)
{
    int iRet =0;
    iRet = functPtr(iData1,iData2);
    return iRet;
}

/*function add two number*/
int AddTwoNumber(int iData1,int iData2)
{
    return (iData1 + iData2);
}
/*function subtract two number*/
int SubTwoNumber(int iData1,int iData2)
{
    return (iData1 - iData2);
}

int main()
{
    int iData1 = 10;
    int iData2 = 3;
    int Result = 0;

    Result = functionWithPointerArgument(iData1,iData2,AddTwoNumber);
    printf("\n\nResult  = %d\n\n",Result);

    Result = functionWithPointerArgument(iData1,iData2,SubTwoNumber);
    printf("\n\nResult  = %d\n\n",Result);

    return 0;
}

Retour d’un pointeur de fonction par une fonction

Exemple :

typedef  int (*pfunctPtr)(int, int); /* function pointer */
        
/*function add two number*/
int AddTwoNumber(int iData1,int iData2)
{
    return (iData1 + iData2);
}
/*function subtract two number*/
int SubTwoNumber(int iData1,int iData2)
{
    return (iData1 - iData2);
}

//Return function pointer
pfunctPtr ArithMaticOperation(int iChoice)
{
    //function pointer
    pfunctPtr pArithmaticFunction = NULL;
    if(iChoice == 1)
    {
        pArithmaticFunction = AddTwoNumber;
    }
    if(iChoice == 2)
    {
        pArithmaticFunction = SubTwoNumber;
    }
    return pArithmaticFunction;
}
        
int main()
{
    int iData1 = 10;
    int iData2 = 3;
    int Result = 0;
    pfunctPtr pArithmaticFunction = NULL; //function pointer

    pArithmaticFunction = ArithMaticOperation(1);
    Result = (*pArithmaticFunction) (iData1,iData2);
    printf("Result  = %d\n\n",Result);

    pArithmaticFunction = ArithMaticOperation(2);
    Result = (*pArithmaticFunction) (iData1,iData2);
    printf("Result  = %d\n\n",Result);

    return 0;
}

Tableau de pointeurs de fonctions

Nous pouvons créer un tableau de pointeurs de fonction.
Ainsi, le tableau de pointeurs de fonctions offre la possibilité d’accéder aux fonctions en utilisant l’index du tableau.

Exemple :

/*function add two number*/
int AddTwoNumber(int iData1,int iData2)
{
    return (iData1 + iData2);
}
/*function subtract two number*/
int SubTwoNumber(int iData1,int iData2)
{
    return (iData1 - iData2);
}
                
int main()
{
    int iRetValue = 0;

    //Declaration of array of function pointer
    int (*apfArithmatics [2])(int,int) = {AddTwoNumber,SubTwoNumber};
        
    //Calling the Add function using index of array
    iRetValue = (*apfArithmatics [0])(20,10);
    printf("\n\nAddition of two number = %d\n\n",iRetValue);
    //Calling the subtract function using index of array
    iRetValue = (*apfArithmatics[1])(20,10);
    printf("\n\nsubtraction of two number = %d\n\n",iRetValue);
        
    return 0;
}

Pointeur de fonction dans les structures

Le C n’est pas un langage orienté objet. Par conséquent, il ne contient pas de fonctions membres comme le C++ par exemple. Mais en utilisant des pointeurs dans une structure, nous pouvons fournir des pointeurs de fonctions traités comme des fonctions membres. Cela permet d’ajouter une notion de polymorphisme en C.

Exemple :

//function pointer to point display message
typedef void (*pfnMessage)(const char*,float fResult);
//function pointer to point arithmetic  function
typedef float (*pfnCalculator)(float,float);

//structure of function pointer
typedef struct S_sArithMaticOperation
{
    float iResult;
    pfnMessage DisplayMessage;
    pfnCalculator ArithmaticOperation;
} sArithMaticOperation;

Programme exemple : Alarme périodique

Voici un petit programme utilisant les pointeurs de fonctions.
L’objectif est d’obtenir un module permettant d’implémenter des alarmes périodiques (timer) qui font appels à des fonctions callback. Il est possible de créer autant d’instances de timer que nécessaire. Au final l’exécution des fonctions callback ont lieu lors de l’appel périodique d’une fonction.

Le code source est disponible sur le dépôt GitHub Function-Pointer-in-C (Pointeur de fonction en C)

Module d’alarme

alarm.h

//Callback Function signature 
typedef int (*fpCallback)(int);

//Structure for alarm data
typedef struct S_sPeriodicAlarm
{
    time_t period;
    int nbrOfAlarm;
    time_t lastTimeAlarm;
    fpCallback callback;
    void* next_sPeriodicAlarm;
}sPeriodicAlarm;
//Pointer to structure for alarm data
typedef sPeriodicAlarm* psPeriodicAlarm;

void initPerodicAlarm(sPeriodicAlarm *periodicAlarm_ptr, time_t period, fpCallback callback);
int checkPeriodicAlarm(sPeriodicAlarm *periodicAlarm_ptr);
void checkAllPeriodicAlarm(void);

Premièrement, un typedef défini la signature des fonctions  »callback ».

Ensuite, la structure sPeriodicAlarm contient plusieurs informations :
La période et la valeur du temps du système lors de la dernière alarme détermine le moment ou une alarme doit être déclenchée.
De plus, la structure contient l’adresse de la fonction callback et l’adresse de la structure de l’alarme suivante (NULL si il n’y a pas d’autre alarme) . Ainsi en stockant le pointeur de l’alarme suivante, il est possible parcourir toute les alarmes.

alarm.c

Initialisation :

#include <time.h> // or #include <sys/time.h>
#include "alarm.h"

volatile psPeriodicAlarm firstPeriodicAlarm_ptr = NULL;
volatile psPeriodicAlarm lastPeriodicAlarm_ptr = NULL;

/**
 * @brief Initialize a periodic alarm with the parameters in arguments
 * 
 * @param periodicAlarm_ptr     Pointer to alarm data structure
 * @param period                Trigger period
 * @param callback              Pointer to callback function
 */
void initPerodicAlarm(sPeriodicAlarm *periodicAlarm_ptr, time_t period, fpCallback callback)
{
    periodicAlarm_ptr->period = period;
    periodicAlarm_ptr->nbrOfAlarm = 0;
    periodicAlarm_ptr->lastTimeAlarm = time(NULL) * 1000;
    periodicAlarm_ptr->callback = callback;
    periodicAlarm_ptr->next_sPeriodicAlarm = NULL;
    
    //Check if is the first alarm
    if(lastPeriodicAlarm_ptr != NULL)
        lastPeriodicAlarm_ptr->next_sPeriodicAlarm = periodicAlarm_ptr;
    else
        firstPeriodicAlarm_ptr = periodicAlarm_ptr;
    
    lastPeriodicAlarm_ptr = periodicAlarm_ptr;
}

Les pointeurs de la première et de la dernière structure d’alarme sont stocker dans les variables globales ‘firstPeriodicAlarm_ptr’ et ‘lastPeriodicAlarm_ptr’.
La fonction d’initialisation permet de configurer l’alarme périodique et d’enregistrer le pointeur de l’alarme actuelle dans la précédente.
Cependant lorsqu’il s’agit de la première alarme, seul le pointeur global de la première structure d’alarme est initialisé.

Vérification de l’occurrence d’une alarme :

/**
 * @brief Check if an alarm is active and execute the associated callback function
 * 
 * @param periodicAlarm_ptr     Pointer to alarm data structure
 * @return int                  Status of callback function
 */
int checkPeriodicAlarm(sPeriodicAlarm *periodicAlarm_ptr)
{
    int ret = -1;
    time_t mnow = time(NULL) * 1000;
    
    if(mnow > (periodicAlarm_ptr->lastTimeAlarm  + periodicAlarm_ptr->period))
    {
        periodicAlarm_ptr->lastTimeAlarm = mnow;
        ++periodicAlarm_ptr->nbrOfAlarm;
        if(periodicAlarm_ptr->callback != NULL)
            ret = periodicAlarm_ptr->callback(periodicAlarm_ptr->nbrOfAlarm);
    }
    
    return ret;
}

Cette fonction vérifie si une alarme doit être déclenchée. Si c’est le cas, le compteur d’alarme est incrémenté et la fonction ‘callback’ est appelée.
Si aucune alarme n’est déclenchée, la fonction retourne « -1 ». Sinon, la fonction retourne la valeur de retour de la fonction ‘callback’.

Vérification de l’occurrence de l’ensemble des alarmes :

/**
 * @brief Checks all the alarms and executes the associated callback functions
 */
void checkAllPeriodicAlarm(void)
{
    psPeriodicAlarm actualPeriodicAlarm_ptr = firstPeriodicAlarm_ptr;
    
    while(actualPeriodicAlarm_ptr != NULL)
    {
        checkPeriodicAlarm(actualPeriodicAlarm_ptr);
        actualPeriodicAlarm_ptr = actualPeriodicAlarm_ptr->next_sPeriodicAlarm;
    }
}

Finalement, cette fonction parcourt l’ensemble des alarmes. En effet, une boucle vérifie l’ensemble des pointeurs de structure d’alarme tant que le pointeur n’est pas NULL.

Programme principal

main.c

#include <stdio.h>
#include <sys/time.h>
#include "alarm.h"

sPeriodicAlarm firstAlarm;
sPeriodicAlarm secondAlarm;

int firstAlarmCallback(int cnt)
{
    printf("First Alarm callback: %d\n\r", cnt);
    return 0;
}

int secondAlarmCallback(int cnt)
{
    printf("Second Alarm callback: %d\n\r", cnt);
    return 0;
}

int main() 
{
    initPerodicAlarm(&firstAlarm, 2000, &firstAlarmCallback);
    initPerodicAlarm(&secondAlarm, 1000, &secondAlarmCallback);
	
    while(1)
    {
        checkAllPeriodicAlarm();
        //Do some stuff
        Sleep(10); //Limit usage of CPU
    }
    return 0;
}

Dans ce programme, nous utilisons deux alarmes périodiques. En premier lieu, elles sont déclarées à l’aide de la structure ‘sPeriodicAlarm’.
Ensuite les alarmes sont initialisées avec la fonction ‘initPerodicAlarm’. Enfin, la vérification et le déclenchement des alarmes sont gérés par l’appel périodique de la fonction ‘checkAllPeriodicAlarm()’.
Il est également envisageable d’utiliser une interruption de timer pour appeler la fonction ‘checkAllPeriodicAlarm()’.

Étiquettes: