Domanda

Ultimamente ho avuto qualche esperienza con i puntatori a funzione in C.

Continuando così con la tradizione di rispondere alle tue domande, ho deciso di fare un piccolo riassunto delle basi, per coloro che hanno bisogno di un rapido approfondimento sull'argomento.

È stato utile?

Soluzione

Puntatori a funzione in C

Cominciamo con una funzione di base che punta a :

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

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

int (*functionPtr)(int,int);

Ora possiamo tranquillamente indicare la nostra funzione:

functionPtr = &addInt;

Ora che abbiamo un puntatore alla funzione, usiamola:

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

Passare il puntatore a un'altra funzione è sostanzialmente lo stesso:

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

Possiamo usare anche i puntatori a 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 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;
}

Altri suggerimenti

I puntatori a 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ì, il -> e la mancanza di un new operatore è un dono morto, ma sembra implicare che stiamo impostando il testo di alcune String classi su "hello".

Utilizzando i puntatori a funzione, è possibile emulare i metodi in C .

Come viene realizzato?

La classe struct è in realtà una newString con una serie di puntatori a funzione che agiscono come un modo per simulare i metodi. Quella che segue è una dichiarazione parziale della getString 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();

Come si può vedere, i metodi della classe get sono in realtà puntatori alla funzione dichiarata. Nel preparare l'istanza di internal, viene chiamata la funzione s1->set("hello"); 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 s1->set(s1, "hello") chiamata chiamando il metodo ImmutableString è definita come la seguente:

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

Una cosa che si può notare è che non esiste il concetto di un'istanza di un oggetto e che abbia metodi che sono in realtà una parte di un oggetto, quindi un " self object " deve essere passato ad ogni invocazione. (E il set è solo un length nascosto che è stato omesso dalla lista dei codici in precedenza - è un modo per eseguire il nascondimento delle informazioni, ma non è rilevante per i puntatori a funzione.)

Quindi, anziché essere in grado di fare char*, è necessario passare l'oggetto per eseguire l'azione su newImmutableString.

Con quella spiegazione minore che deve passare un riferimento a te stesso, ti sposteremo alla parte successiva, che è eredità in C .

Supponiamo di voler creare una sottoclasse di String.get, diciamo String.length. Per rendere immutabile la stringa, il metodo base non sarà accessibile, mantenendo l'accesso a 0 e lengthOverrideMethod e forzando il & Quot; costruttore & Quot; per accettare un <=>:

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 a funzione. Questa volta, la dichiarazione per il metodo <=> non è presente, pertanto non può essere richiamata in <=>.

Per quanto riguarda l'implementazione di <=>, l'unico codice rilevante è il " costruttore " funzione, il <=>:

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

Nell'istanza di <=>, i puntatori di funzione ai metodi <=> e <=> si riferiscono effettivamente al metodo <=> e <=>, passando attraverso la variabile <=> che è una memoria interna < => oggetto.

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

Possiamo continuare a polimorfismo in C .

Se per esempio volessimo cambiare il comportamento del metodo <=> per restituire <=> sempre nella classe <=> per qualche motivo, tutto ciò che dovrebbe essere fatto è:

  1. Aggiungi una funzione che fungerà da metodo <=> prioritario.
  2. Vai al " costruttore " e imposta il puntatore a funzione sul metodo <=> di sostituzione.

L'aggiunta di un metodo prioritario <=> in <=> può essere eseguita aggiungendo un <=>:

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

Quindi, il puntatore a funzione per il metodo <=> nel costruttore è collegato a <=>:

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 <=> nella classe <=> rispetto alla classe <=>, ora il metodo <=> farà riferimento al comportamento definito nella funzione <=>.

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 punti che non ho spiegato bene, o che potrebbero non essere chiari in termini di migliore implementare OOP in C. Ma il mio scopo era quello di provare a illustrare uno dei molti usi dei puntatori a funzione.

Per ulteriori informazioni su come eseguire la programmazione orientata agli oggetti in C, fare riferimento alle seguenti domande:

La guida per essere licenziato: come abusare dei puntatori a funzioni 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 x86 ret .

Normalmente non li scriveresti a mano, scriveresti in linguaggio assembly e poi utilizzeresti un assemblatore come nasm per assemblarlo in un binario piatto che esegui il hex in un letterale di stringa C.

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

Nota che i compilatori inseriscono valori letterali stringa 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 l'autorizzazione Read + Exec, quindi il cast dei letterali di stringa per i puntatori funziona senza la necessità di mprotect() o VirtualProtect() chiamate di sistema come avresti bisogno per la memoria allocata dinamicamente. (Oppure gcc -z execstack collega il programma con stack + segmento di dati + eseguibile heap, come hack rapido.)


Per smontarli, 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'assemblaggio e scoprire che questo codice viola l'ABI bloccando EBX (un registro protetto dalle 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 nello stack anziché in modo più efficiente nei registri. Ma EBX è preservato dalle chiamate in tutte le normali convenzioni di chiamata, quindi usarlo come un registro scratch senza salvarlo / ripristinarlo può facilmente causare l'arresto anomalo del chiamante.

Uno dei miei usi preferiti per i puntatori a funzioni è come iteratori economici e facili -

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

I puntatori a funzione diventano facili da dichiarare una volta che hai i dichiaratori di base:

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

Mentre D è un altro dichiaratore creato usando le stesse regole. Alla fine, da qualche parte, termina con [ (vedi sotto per un esempio), che è il nome dell'entità dichiarata. Proviamo a costruire una funzione prendendo un puntatore a una funzione che non prende nulla e restituisce int e restituisce un puntatore a una funzione che prende un carattere e restituisce int. Con i tipi-def è così

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

Come vedi, è abbastanza facile costruirlo usando typedefs. Senza typedefs, non è nemmeno difficile con le regole del dichiaratore sopra, applicate in modo coerente. Come vedi ho perso la parte a cui punta il puntatore e la cosa che la funzione ritorna. Questo è ciò che appare alla sinistra della dichiarazione e non è di interesse: viene aggiunto alla fine se si è già creato il dichiarante. Facciamolo. Costruendolo in modo coerente, prima parola: mostra la struttura usando ] e D1:

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

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

Bottom Up

La costruzione inizia con la cosa a destra: la cosa restituita, che è la funzione che prende char. Per mantenere distinti i dichiaranti, li numererò:

D1(char);

Inserito direttamente il parametro char, poiché è banale. Aggiunta di un puntatore al dichiaratore sostituendo *D2 con *-operator. Nota che dobbiamo racchiudere le parentesi tra (). Questo può essere conosciuto cercando la precedenza di *(D2(char p)) e l'operatore di chiamata di funzione D2. Senza le nostre parentesi, il compilatore lo leggerebbe come <parameters>. Ma ciò non sarebbe più una semplice sostituzione di D1 con D3(<parameters>), ovviamente. Le parentesi sono sempre consentite intorno ai dichiaratori. Quindi non fai nulla di sbagliato se ne aggiungi troppi, in realtà.

(*D2)(char);

Il tipo restituito è completo! Ora, sostituiamo D3 con il dichiaratore di funzione che accetta char ritorno , che è void che siamo ora.

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

Nota che non sono necessarie parentesi, poiché vogliamo ID1 essere un dichiaratore di funzione e non un dichiaratore di puntatore questa volta. Fantastico, l'unica cosa che rimane sono i parametri per questo. Il parametro è fatto esattamente come il tipo restituito, solo con int sostituito da ID0. Quindi lo copierò:

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

Ho sostituito * con (char), dato che abbiamo finito con quel parametro (è già un puntatore a una funzione - non c'è bisogno di un altro dichiaratore). <=> 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 restituito. Per i puntatori il tipo appuntito ecc ... È interessante quando si scrive il tipo, apparirà nell'ordine opposto, proprio a destra :) Comunque, sostituendolo si ottiene la dichiarazione completa. Entrambe le volte <=> ovviamente.

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

In questo esempio ho chiamato l'identificatore della funzione <=>.

Top Down

Questo inizia dall'identificatore all'estrema sinistra nella descrizione del tipo, avvolgendo quel dichiaratore mentre percorriamo la strada a destra. Inizia con la funzione che accetta <=> parametri <=> ritorno

ID0(<parameters>)

La cosa successiva nella descrizione (dopo " restituendo ") era puntatore a . Incorporiamolo:

*ID0(<parameters>)

Quindi la cosa successiva è stata functon prendendo <=> parametri <=> ritorno . Il parametro è un semplice carattere, quindi lo inseriamo subito, dato che è rdavvero banale.

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

Nota le parentesi che abbiamo aggiunto, poiché desideriamo nuovamente che <=> si leghi prima e quindi il <=>. Altrimenti leggerebbe la funzione prendendo <=> parametri <=> funzione di ritorno ... . No, le funzioni che restituiscono funzioni non sono nemmeno consentite.

Ora non ci resta che inserire <=> parametri <=>. Mostrerò una versione breve della deriveration, dal momento che penso che tu abbia già l'idea di come farlo.

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

Metti <=> davanti ai dichiaranti come abbiamo fatto con il bottom-up e abbiamo finito

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

La cosa carina

Il bottom-up o il top-down sono migliori? Sono abituato a fare il bottom-up, ma alcune persone potrebbero sentirsi più a proprio agio con il top-down. Penso che sia una questione di gusti. Per inciso, se si applicano tutti gli operatori in quella dichiarazione, si otterrà un int:

<*>

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

Spero ti sia piaciuto questo piccolo tutorial! Ora possiamo collegarci a questo quando le persone si chiedono la strana sintassi delle dichiarazioni. Ho provato a mettere il minor numero di interni C possibile. Sentiti libero di modificare / riparare le cose in esso.

Un altro buon uso dei puntatori a funzioni:
Passaggio da una versione all'altra indolore

Sono molto utili da usare quando vuoi funzioni diverse in momenti diversi o fasi di sviluppo diverse. Ad esempio, sto sviluppando un'applicazione su un computer host che ha una console, ma la versione finale del software verrà inserita in un 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 i messaggi di stato e di errore, ma quando avrò 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

In version.c Definirò i 2 prototipi di funzione 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;
}

Notare come il puntatore a funzione è prototipato in void (* zprintf)(const char *, ...); come

board_init()

Quando viene indicato nell'applicazione, verrà avviato eseguendo ovunque sia puntato, che deve ancora essere definito.

In zprintf, notare nella funzione zprintf = &printf; in cui zprintf = &noprint; è assegnata una funzione unica (la cui firma della funzione corrisponde) a seconda della versione definita in <=>

<= > zprintf chiama printf a scopo di debug

o

<=> zprintf ritorna e non esegue codice non necessario

L'esecuzione del codice sarà simile al seguente:

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 precedente utilizzerà <=> se in modalità debug o non farà nulla se in modalità di rilascio. Questo è molto più semplice che passare attraverso l'intero progetto e commentare o eliminare il codice. Tutto quello che devo fare è cambiare la versione in <=> e il codice farà il resto!

Il puntatore a funzione è generalmente definito da typedef e usato come parametro & amp; valore di ritorno.

Le risposte di cui sopra hanno già spiegato molto, faccio 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;
}

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

Un esempio molto semplice, se esiste una funzione chiamata print(int x, int y) che a sua volta potrebbe richiedere di chiamare una funzione (add() o sub(), che sono dello stesso tipo), allora cosa faremo, noi aggiungerà un argomento puntatore alla funzione print() come mostrato di seguito:

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

L'output è:

  

il valore è: 410
  il valore è: 390

Un puntatore a funzione è una variabile che contiene l'indirizzo di una funzione. Dal momento che è una variabile puntatore sebbene con alcune proprietà limitate, puoi usarlo praticamente come faresti con qualsiasi altra variabile puntatore nelle strutture dati.

L'unica eccezione che mi viene in mente è quella di trattare il puntatore a funzione come se indicasse qualcosa di diverso da un singolo valore. Fare l'aritmetica del puntatore incrementando o decrementando un puntatore a funzione o aggiungendo / sottraendo un offset a un puntatore a funzione non è in realtà alcuna utilità poiché un puntatore a funzione punta solo a una singola cosa, il punto di ingresso di una funzione.

La dimensione di una variabile del puntatore a funzione, il numero di byte occupati dalla variabile, può variare a seconda dell'architettura sottostante, ad es. x32 o x64 o altro.

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

Alcuni esempi:

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

  • func è una funzione che accetta un int e un char * e restituisce un pFunc
  • func() è un puntatore a cui è assegnato l'indirizzo di una funzione che accetta un pFunc = func; e un if e restituisce un void

Quindi da quanto sopra potremmo avere una linea sorgente in cui l'indirizzo della funzione namespace è assegnato alla variabile del puntatore alla funzione struct come in static.

Notare la sintassi utilizzata con una dichiarazione / definizione di puntatore a funzione in cui le parentesi vengono utilizzate per superare le regole di precedenza dell'operatore naturale.

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

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 di 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 la possibilità per il compilatore C di eseguire controlli sull'elenco degli argomenti fornito.

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

Cast in stile C

Puoi usare i cast in stile C con i puntatori a funzione. Tuttavia, tieni presente che un compilatore C potrebbe essere in ritardo sui controlli o fornire avvisi anziché 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 a funzione con l'uguaglianza

Puoi verificare che un puntatore a funzione sia uguale a un indirizzo di funzione particolare usando un'istruzione const anche 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 a funzione

E se si desidera avere una matrice di puntatori a funzione ciascuno degli elementi di cui l'elenco degli argomenti presenta differenze, è possibile definire un puntatore a funzione con l'elenco degli argomenti non specificato (non qsort() che significa nessun argomento ma solo non specificato) qualcosa di simile al seguente anche se è possibile che vengano visualizzati avvisi dal compilatore C. Questo funziona anche per un parametro del puntatore a 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 bsearch() Uso globale <=> con puntatori a funzione

È possibile utilizzare la parola chiave <=> per specificare una funzione il cui nome è ambito del file e quindi assegnarla a una variabile globale come modo per fornire qualcosa di simile alla <=> funzionalità di C ++.

In un file di intestazione definire una struttura che sarà il nostro spazio dei nomi insieme a una variabile globale che lo utilizza.

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 verrebbe quindi usato specificando il nome completo della variabile strutturale globale e il nome del membro per accedere alla funzione. Il modificatore <=> viene utilizzato a livello globale in modo che non possa essere modificato accidentalmente.

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

Aree di applicazione dei puntatori a funzione

Un componente di libreria DLL potrebbe fare qualcosa di simile all'approccio in stile C <=> in cui una particolare interfaccia di libreria è richiesta da un metodo di fabbrica in un'interfaccia di libreria che supporta la creazione di un <=> che contiene puntatori a funzioni. Questo l'interfaccia della libreria carica la versione DLL richiesta, crea una struttura con i puntatori di funzione necessari e 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 modello particolare dell'hardware sottostante. I puntatori a funzione sono compilati con funzioni specifiche dell'hardware da una fabbrica per fornire la funzionalità specifica dell'hardware che implementa le funzioni specificate nel modello hardware astratto. Questo può essere utilizzato per fornire un livello hardware astratto utilizzato dal software che chiama una funzione di fabbrica al fine di ottenere l'interfaccia di funzione hardware specifica, quindi utilizza i puntatori di funzione forniti per eseguire azioni per l'hardware sottostante senza la necessità di conoscere i dettagli di implementazione sulla destinazione specifica .

Puntatori a funzione per creare delegati, gestori e callback

È possibile utilizzare i puntatori a funzione come modo per delegare alcune attività o funzionalità. L'esempio classico in C è il puntatore alla funzione di delegato di confronto utilizzato con le funzioni della libreria C standard <=> e <=> per fornire l'ordine di confronto per ordinare un elenco di elementi o eseguire una ricerca binaria su un elenco ordinato di elementi. Il delegato della funzione di confronto specifica l'algoritmo di confronto utilizzato nell'ordinamento o nella ricerca binaria.

Un altro uso è simile all'applicazione di un algoritmo a un contenitore Libreria di modelli 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 viene registrato un gestore per un particolare evento 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 recapitati a una finestra o thread.

Le funzioni asincrone che richiedono un callback sono simili a un gestore 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.

La funzione di avvio da zero ha un indirizzo di memoria da cui iniziano l'esecuzione. Nel linguaggio assembly Vengono chiamati come (call & Quot; indirizzo di memoria della funzione & Quot;). Ora ritorna a C Se la funzione ha un indirizzo di memoria, possono essere manipolati da Puntatori in C.So Secondo le regole di C

1. Innanzitutto è necessario dichiarare un puntatore per funzionare 2.Passare l'indirizzo della funzione desiderata

**** Nota - > le funzioni dovrebbero 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");
}

inserisci qui la descrizione dell'immagine After That ti permette di vedere come la macchina li capisce.Glimpse of machine machine of the above program in architettura a 32 bit.

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

Dato che i puntatori a funzione sono spesso callback tipizzati, potresti dare un'occhiata a digitare callback sicuri . Lo stesso vale per i punti di ingresso, ecc. Di funzioni che non sono richiamate.

C è piuttosto volubile e perdona allo stesso tempo :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top