function-pointers puntatore - Come funzionano i puntatori di funzione in C?





membro c++ (11)


Un altro buon uso per i puntatori di funzione:
Passaggio tra le versioni senza dolore

Sono molto utili da utilizzare quando si desiderano funzioni diverse in momenti diversi o in fasi di sviluppo diverse. Ad esempio, sto sviluppando un'applicazione su un computer host con una console, ma la versione finale del software verrà messa su Avnet ZedBoard (che ha porte per display e console, ma non sono necessarie / ricercate per il rilascio finale). Quindi durante lo sviluppo userò printf per visualizzare lo stato e i messaggi di errore, ma quando ho finito, non voglio stampare nulla. Ecco cosa ho fatto:

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

Nella version.c i 2 prototipi di funzioni presenti in 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;
}

Si noti come il puntatore della funzione è prototipato in version.h come

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

Quando viene fatto riferimento nell'applicazione, inizierà l'esecuzione ovunque sia puntato, che deve ancora essere definito.

In version.c , si noti nella funzione board_init() dove a zprintf è assegnata una funzione unica (la cui firma di funzione corrisponde) a seconda della versione che è definita in version.h

zprintf = &printf; zprintf chiama printf a scopo di debug

o

zprintf = &noprint; zprintf restituisce e non eseguirà codice non necessario

L'esecuzione del codice sarà simile a questa:

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

Il codice sopra utilizzerà printf se in modalità debug, o non fare nulla se in modalità di rilascio. Questo è molto più facile che passare attraverso l'intero progetto e commentare o eliminare il codice. Tutto quello che devo fare è cambiare la versione in version.h e il codice farà il resto!

Ho avuto qualche esperienza ultimamente con i puntatori di funzione in C.

Quindi, continuando con la tradizione di rispondere alle tue domande, ho deciso di fare un piccolo riassunto delle basi stesse, per coloro che hanno bisogno di una veloce immersione nell'argomento.




Poiché i puntatori di funzione sono spesso callback tipizzati, è possibile dare un'occhiata al tipo callback sicuri . Lo stesso vale per i punti di ingresso, ecc. Di funzioni che non sono callback.

C è abbastanza volubile e tollerante allo stesso tempo :)




Il puntatore di funzione è solitamente definito da typedef e utilizzato come parametro e valore di ritorno,

Sopra le risposte già spiegate molto, do solo un esempio completo:

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



Un puntatore a funzione è una variabile che contiene l'indirizzo di una funzione. Poiché si tratta di una variabile puntatore con alcune proprietà limitate, è possibile utilizzarla praticamente come qualsiasi altra variabile puntatore nelle strutture dati.

L'unica eccezione a cui riesco a pensare è il fatto che il puntatore della funzione indichi qualcosa di diverso da un singolo valore. Fare aritmetica del puntatore incrementando o decrementando un puntatore a funzione o aggiungendo / sottraendo un offset a un puntatore a funzione non è in realtà di alcuna utilità in quanto un puntatore a funzione punta solo a una singola cosa, il punto di ingresso di una funzione.

La dimensione di una variabile di puntatore a funzione, il numero di byte occupati dalla variabile, può variare a seconda dell'architettura sottostante, ad esempio x32 o x64 o qualsiasi altra cosa.

La dichiarazione per una variabile di puntatore a funzione deve specificare lo stesso tipo di informazione di una dichiarazione di funzione in modo che il compilatore C esegua i tipi di controlli che normalmente esegue. Se non si specifica un elenco di parametri nella dichiarazione / definizione del puntatore della funzione, il compilatore C non sarà in grado di controllare l'uso dei parametri. Ci sono casi in cui questa mancanza di controllo può essere utile, ma ricorda solo che è stata rimossa una rete di sicurezza.

Qualche esempio:

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.

Le prime due dichiarazioni sono in qualche modo simili in quanto:

  • func è una funzione che accetta un int e un char * e restituisce un int
  • pFunc è un puntatore a funzione a cui viene assegnato l'indirizzo di una funzione che accetta un int e un char * e restituisce un int

Quindi da quanto sopra potremmo avere una linea sorgente in cui l'indirizzo della funzione func() è assegnato alla variabile del puntatore function pFunc come in pFunc = func; .

Si noti la sintassi utilizzata con una dichiarazione / definizione del puntatore di funzione in cui vengono utilizzate le parentesi per superare le naturali regole di precedenza degli operatori.

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

Diversi esempi di utilizzo diversi

Alcuni esempi di utilizzo di un puntatore a funzione:

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

È possibile utilizzare elenchi di parametri a lunghezza variabile nella definizione di un puntatore a funzione.

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

Oppure non è possibile specificare un elenco di parametri. Questo può essere utile ma elimina l'opportunità per il compilatore C di eseguire controlli sulla lista degli argomenti fornita.

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 stile Casts

È possibile utilizzare i cast di stile C con i puntatori di funzione. Tuttavia, si tenga presente che un compilatore C potrebbe non essere a conoscenza dei controlli o fornire avvertenze piuttosto che errori.

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.

Confronta il puntatore funzione su uguaglianza

È possibile verificare che un puntatore a funzione sia uguale a un indirizzo di una particolare funzione utilizzando un'istruzione if se non sono sicuro di quanto sarebbe utile. Altri operatori di confronto sembrerebbero avere ancora meno 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;
}

Una matrice di puntatori funzione

E se vuoi avere una serie di puntatori di funzione, ognuno degli elementi di cui l'elenco degli argomenti ha delle differenze, puoi definire un puntatore di funzione con l'elenco degli argomenti non specificato (non voidche significa nessun argomento ma solo non specificato) qualcosa come la seguente anche se tu potrebbe vedere gli avvisi dal compilatore C. Questo funziona anche per un parametro del puntatore di funzione per una funzione:

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

Stile C namespaceUso globale structcon puntatori di funzioni

È possibile utilizzare la staticparola chiave per specificare una funzione il cui nome è scope del file e quindi assegnarlo a una variabile globale come metodo per fornire qualcosa di simile alla namespacefunzionalità di C ++.

In un file di intestazione definiamo una struttura che sarà il nostro spazio dei nomi insieme a una variabile globale che la usa.

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;

Quindi nel file sorgente 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};

Questo sarebbe quindi usato specificando il nome completo della variabile struct globale e il nome del membro per accedere alla funzione. Il constmodificatore viene utilizzato su tutto il mondo in modo che non possa essere modificato per sbaglio.

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

Aree applicative dei puntatori funzione

Un componente della libreria DLL potrebbe fare qualcosa di simile namespaceall'approccio in stile C in cui è richiesta una particolare interfaccia di libreria da un metodo factory in un'interfaccia di libreria che supporta la creazione di un structpuntatore a funzione contenente. Questa interfaccia libreria carica la versione richiesta DLL, crea una struttura con i necessari puntatori di funzione, quindi restituisce la struttura al chiamante richiedente per l'uso.

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

e questo potrebbe essere usato come in:

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

Lo stesso approccio può essere utilizzato per definire un livello hardware astratto per il codice che utilizza un particolare modello dell'hardware sottostante. I puntatori di funzione sono compilati da una funzione specifica dell'hardware da una fabbrica per fornire la funzionalità specifica dell'hardware che implementa le funzioni specificate nel modello hardware astratto. Questo può essere usato per fornire un livello hardware astratto usato dal software che richiama una funzione di fabbrica per ottenere l'interfaccia di funzione hardware specifica, quindi utilizza i puntatori di funzione forniti per eseguire azioni per l'hardware sottostante senza dover conoscere i dettagli dell'implementazione sul target specifico .

Funzione Puntatori per creare delegati, gestori e callback

È possibile utilizzare i puntatori di funzione come metodo per delegare alcune attività o funzionalità. L'esempio classico di C è il puntatore alla funzione delegate di confronto utilizzato con le funzioni della libreria C standard qsort()e bsearch()per fornire l'ordine di confronto per l'ordinamento di un elenco di elementi o l'esecuzione di una ricerca binaria su un elenco ordinato di elementi. La funzione di confronto delegato specifica l'algoritmo di confronto utilizzato nell'ordinamento o nella ricerca binaria.

Un altro uso è simile all'applicazione di un algoritmo a un contenitore di libreria modello 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 altro esempio è con il codice sorgente della GUI in cui un gestore per un particolare evento è registrato fornendo un puntatore a funzione che viene effettivamente chiamato quando si verifica l'evento. Il framework Microsoft MFC con le sue mappe dei messaggi utilizza qualcosa di simile per gestire i messaggi di Windows che vengono consegnati a una finestra o thread.

Le funzioni asincrone che richiedono una richiamata sono simili a un gestore di eventi. L'utente della funzione asincrona chiama la funzione asincrona per avviare un'azione e fornisce un puntatore a funzione che la funzione asincrona chiamerà una volta completata l'azione. In questo caso l'evento è la funzione asincrona che completa il suo compito.




Puntatori di funzione in C

Iniziamo con una funzione di base che punteremo a :

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

Per prima cosa, definiamo un puntatore a una funzione che riceve 2 int e restituisce un int :

int (*functionPtr)(int,int);

Ora possiamo tranquillamente indicare la nostra funzione:

functionPtr = &addInt;

Ora che abbiamo un puntatore alla funzione, usiamolo:

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

Passare il puntatore ad un'altra funzione è fondamentalmente la stessa:

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

Possiamo usare anche i puntatori di funzione nei valori di ritorno (prova a tenere il passo, diventa disordinato):

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

Ma è molto più bello usare 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;
}



La guida per essere licenziato: Come abusare dei puntatori di funzione in GCC su macchine x86 compilando il codice a mano:

Questi valori letterali stringa sono byte di codice macchina x86 a 32 bit. 0xC3 è un'istruzione ret x86 .

Normalmente non si scriveranno questi a mano, si scriverà in linguaggio assembly e quindi si userà un assemblatore come nasm per assemblarlo in un binario piatto che si esegue l'hexdump in una stringa C letterale.

  1. Restituisce il valore corrente sul registro EAX

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. Scrivi una funzione di scambio

    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. Scrivi un contatore for-loop a 1000, chiamando ogni volta una funzione

    ((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. Puoi persino scrivere una funzione ricorsiva che conta fino a 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);
    

Si noti che i compilatori inseriscono stringhe letterali nella sezione .rodata (o .rdata su Windows), che è collegata come parte del segmento di testo (insieme al codice per le funzioni).

Il segmento di testo ha permesso di lettura + Exec, quindi il casting di stringhe letterali per i puntatori di funzione funziona senza richiedere le chiamate di sistema mprotect() o VirtualProtect() come se fosse necessario per la memoria allocata dinamicamente. (O gcc -z execstack collega il programma con stack + segmento di dati + heap eseguibile, come un rapido hack.)

Per disassemblarli, puoi compilarlo per mettere un'etichetta sui byte e usare un disassemblatore.

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

Compilando con gcc -c -m32 foo.c e disassemblando con objdump -D -rwC -Mintel , possiamo ottenere l'assembly e scoprire che questo codice viola l'ABI clobbering EBX (un registro conservato per le chiamate) ed è generalmente inefficiente .

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

Questo codice macchina funzionerà (probabilmente) in codice a 32 bit su Windows, Linux, OS X e così via: le convenzioni di chiamata predefinite su tutti quei sistemi operativi passano gli argomenti sullo stack anziché in modo più efficiente nei registri. Ma EBX è conservato in tutte le normali convenzioni di chiamata, quindi usarlo come un registro scratch senza salvare / ripristinarlo può facilmente far crashare il chiamante.




La funzione di partenza da zero ha alcuni indirizzi di memoria da dove iniziano l'esecuzione. In Assembly Language sono chiamati come (chiama "indirizzo di memoria della funzione"). Ora torna a C Se la funzione ha un indirizzo di memoria, possono essere manipolati da Puntatori in C.So Secondo le regole di C

1.Prima è necessario dichiarare un puntatore a funzione 2.Passare l'indirizzo della funzione desiderata

**** Nota-> le funzioni devono essere dello stesso tipo ****

Questo semplice programma illustrerà ogni cosa.

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

After That consente di vedere come Machine Understands Them.Glimpse delle istruzioni della macchina del programma sopra nell'architettura a 32 bit.

L'area del segno rosso mostra come l'indirizzo viene scambiato e memorizzato in eax. Quindi la loro è un'istruzione di chiamata su eax. eax contiene l'indirizzo desiderato della funzione




I puntatori di funzione in C possono essere utilizzati per eseguire la programmazione orientata agli oggetti in C.

Ad esempio, le seguenti righe sono scritte in C:

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

Sì, la -> e la mancanza di un new operatore è una donazione morta, ma di certo sembra implicare che stiamo impostando il testo di qualche classe String come "hello" .

Usando i puntatori di funzione, è possibile emulare i metodi in C.

Come è stato realizzato?

La classe String è in realtà una struct con un gruppo di puntatori a funzione che agiscono come un modo per simulare i metodi. Di seguito è riportata una dichiarazione parziale della 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();

Come si può vedere, i metodi della classe String sono in realtà dei puntatori alla funzione dichiarata. Nella preparazione dell'istanza della String , viene chiamata la funzione newString per impostare i puntatori di funzione alle rispettive funzioni:

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

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

    self->set(self, "");

    return self;
}

Ad esempio, la funzione getString chiamata invocando il metodo get è definita come segue:

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

Una cosa che può essere notata è che non esiste un concetto di un'istanza di un oggetto e di avere metodi che sono effettivamente una parte di un oggetto, quindi un "oggetto autonomo" deve essere passato in ogni invocazione. (E l' internal è solo una struct nascosta che è stata omessa dal codice che precede l'elenco - è un modo di nascondere le informazioni, ma ciò non è rilevante per i puntatori di funzione.)

Quindi, piuttosto che essere in grado di fare s1->set("hello"); , si deve passare nell'oggetto per eseguire l'azione su s1->set(s1, "hello") .

Con questa piccola spiegazione che deve passare in un riferimento a te stesso di mezzo, passeremo alla parte successiva, che è l' ereditarietà in C.

Diciamo che vogliamo creare una sottoclasse di String , ad esempio ImmutableString . Per rendere la stringa immutabile, il metodo set non sarà accessibile, pur mantenendo l'accesso a get e length , e costringerà il "costruttore" ad accettare 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);

Fondamentalmente, per tutte le sottoclassi, i metodi disponibili sono ancora una volta puntatori di funzione. Questa volta, la dichiarazione per il metodo set non è presente, quindi non può essere chiamata in ImmutableString .

Per quanto riguarda l'implementazione di ImmutableString , l'unico codice rilevante è la funzione "costruttore", 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;
}

String.get String.length , i puntatori di funzione ai metodi get e length riferiscono effettivamente al metodo String.get e String.length , passando per la variabile base che è un oggetto String memorizzato internamente.

L'uso di un puntatore a funzione può ottenere l'ereditarietà di un metodo da una superclasse.

Possiamo continuare ulteriormente con il polimorfismo in C.

Se per esempio volessimo cambiare il comportamento del metodo length per restituire 0 tutto il tempo nella classe ImmutableString per qualche ragione, tutto ciò che dovrebbe essere fatto è:

  1. Aggiungi una funzione che servirà come metodo di length prevalente.
  2. Vai al "costruttore" e imposta il puntatore alla funzione sul metodo della length override.

L'aggiunta di un metodo di length sovrascritta in ImmutableString può essere eseguita aggiungendo un lengthOverrideMethod :

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

Quindi, il puntatore della funzione per il metodo length nel costruttore è collegato a 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;
}

Ora, anziché avere un comportamento identico per il metodo length nella classe ImmutableString come la classe String , ora il metodo length si riferirà al comportamento definito nella funzione lengthOverrideMethod .

Devo aggiungere una dichiarazione di non responsabilità che sto ancora imparando a scrivere con uno stile di programmazione orientato agli oggetti in C, quindi probabilmente ci sono dei punti che non ho spiegato bene, o che potrebbero semplicemente essere off-mark in termini di come implementare OOP al meglio in C. Ma il mio scopo era cercare di illustrare uno dei molti usi dei puntatori di funzione.

Per ulteriori informazioni su come eseguire la programmazione orientata agli oggetti in C, consultare le seguenti domande:




I puntatori alle funzioni diventano facili da dichiarare una volta che hai i dichiaratori di base:

  • id: ID : ID è un
  • Puntatore: *D : D puntatore a
  • Funzione: D(<parameters>) : funzione D che richiede < parametri > ritorno

Mentre D è un altro dichiaratore costruito usando le stesse regole. Alla fine, da qualche parte, termina con ID (vedi sotto per un esempio), che è il nome dell'entità dichiarata. Proviamo a costruire una funzione prendendo un puntatore a una funzione senza nulla e restituendo int, e restituendo un puntatore a una funzione prendendo un char e restituendo int. Con type-defs è così

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

Come vedi, è piuttosto facile crearlo usando typedef. Senza typedef, non è difficile né con le regole dichiarator sopra, applicate coerentemente. Come vedi ho perso la parte a cui punta il puntatore e la cosa che la funzione restituisce. Questo è ciò che appare alla sinistra della dichiarazione, e non è di interesse: è aggiunto alla fine se si è già costruito il dichiaratore. Facciamolo. Costruirlo coerentemente, in primo luogo wordy - mostrando la struttura usando [ e ] :

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

Come vedi, si può descrivere un tipo completamente accodando i dichiaratori uno dopo l'altro. La costruzione può essere fatta in due modi. Uno è dal basso verso l'alto, iniziando dalla cosa giusta (foglie) e procedendo fino all'identificatore. L'altro modo è dall'alto verso il basso, a partire dall'identificatore, procedendo fino alle foglie. Mostrerò entrambi i modi.

Dal basso verso l'alto

La costruzione inizia con la cosa alla destra: la cosa è tornata, che è la funzione che prende il char. Per mantenere distinti i dichiaranti, ho intenzione di numerarli:

D1(char);

Inserito direttamente il parametro char, dato che è banale. Aggiungere un puntatore al dichiaratore sostituendo D1 con *D2 . Si noti che dobbiamo racchiudere parentesi attorno a *D2 . Questo può essere conosciuto cercando la precedenza di *-operator e della funzione call operator () . Senza le nostre parentesi, il compilatore lo leggerà come *(D2(char p)) . Ma non sarebbe più una semplice sostituzione di D1 di *D2 , naturalmente. Le parentesi sono sempre consentite attorno ai dichiaratori. Quindi non fai nulla di male se ne aggiungi troppo, in realtà.

(*D2)(char);

Il tipo di reso è completo! Ora sostituiamo D2 con la funzione dichiaratore della funzione che richiede il ritorno di <parameters> , che è D3(<parameters>) cui ci troviamo ora.

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

Nota che non sono necessarie parentesi, dal momento che vogliamo che D3 sia un dichiaratore di funzioni e non un dichiaratore di puntatore questa volta. Grande, l'unica cosa rimasta sono i parametri per questo. Il parametro è fatto esattamente come abbiamo fatto con il tipo restituito, solo con char sostituito da void . Quindi lo copierò:

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

Ho sostituito D2 con ID1 , dato che abbiamo finito con quel parametro (è già un puntatore a una funzione - non c'è bisogno di un altro dichiarante). ID1 sarà il nome del parametro. Ora, ho detto sopra alla fine si aggiunge il tipo che tutti quei dichiaranti modificano - quello che appare alla sinistra di ogni dichiarazione. Per le funzioni, questo diventa il tipo di ritorno. Per i puntatori punta a digitare ecc ... È interessante quando si scrive il tipo, apparirà nell'ordine opposto, all'estrema destra :) Comunque, sostituendolo si ottiene la dichiarazione completa. Entrambe le volte, naturalmente.

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

Ho chiamato l'identificatore della funzione ID0 in quell'esempio.

Dall'alto al basso

Questo inizia dall'identificatore all'estrema sinistra nella descrizione del tipo, avvolgendo quel dichiaratore mentre camminiamo attraverso la destra. Inizia con la funzione che richiede < parametri > ritorno

ID0(<parameters>)

La prossima cosa nella descrizione (dopo "ritorno") era puntatore a . Incorporiamolo:

*ID0(<parameters>)

Poi la prossima cosa è stata la functon che stava facendo < parametri > . Il parametro è un semplice carattere, quindi lo inseriamo subito, poiché è davvero banale.

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

Nota le parentesi che abbiamo aggiunto, dal momento che vogliamo nuovamente che * leghi prima e poi (char) . Altrimenti leggerebbe la funzione prendendo < parameters > restituendo la funzione .... No, le funzioni che restituiscono funzioni non sono nemmeno consentite.

Ora abbiamo solo bisogno di mettere < parametri > . Mostrerò una versione breve della deriverazione, poiché penso che tu abbia già ora l'idea di come farlo.

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

Basta inserire prima i dichiaranti come abbiamo fatto con il bottom-up, e abbiamo finito

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

La cosa bella

Il bottom-up o il top-down sono migliori? Sono abituato al bottom-up, ma alcune persone potrebbero essere più a loro agio con il top-down. È una questione di gusti, penso. Per inciso, se si applicano tutti gli operatori in quella dichiarazione, si finirà per ottenere un int:

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

Questa è una bella proprietà delle dichiarazioni in C: la dichiarazione afferma che se quegli operatori sono usati in un'espressione usando l'identificatore, allora restituisce il tipo all'estrema sinistra. È così anche per gli array.

Spero ti sia piaciuto questo piccolo tutorial! Ora possiamo collegarci a questo quando le persone si interrogano sulla strana sintassi di dichiarazione delle funzioni. Ho provato a mettere il più piccolo C internals possibile. Sentiti libero di modificare / sistemare le cose in esso.




Uno dei grandi usi per i puntatori di funzione in C è di chiamare una funzione selezionata in fase di esecuzione. Ad esempio, la libreria di runtime C ha due routine, qsort e bsearch, che accettano un puntatore a una funzione chiamata per confrontare due elementi ordinati; questo ti consente di ordinare o cercare, rispettivamente, qualsiasi cosa, in base a qualsiasi criterio tu desideri utilizzare.

Un esempio molto semplice, se esiste una funzione chiamata print (int x, int y) che a sua volta può richiedere di chiamare add () function o sub () che sono di tipi simili allora cosa faremo, aggiungeremo una funzione argomento puntatore alla funzione print () come mostrato di seguito: -

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



Utilizzare uno degli operatori come definito here .

Per impostare un bit, utilizzato int x = x | 0x?;dove ?è la posizione del bit in forma binaria.





c function-pointers