Puntatore a funzione C che esegue il puntamento al puntatore del vuoto

Sto cercando di eseguire il seguente programma ma ottenendo alcuni strani errori:

File 1.c:

typedef unsigned long (*FN_GET_VAL)(void); FN_GET_VAL gfnPtr; void setCallback(const void *fnPointer) { gfnPtr = *((FN_GET_VAL*) (&fnPointer)); } 

File 2.c:

 extern FN_GET_VAL gfnPtr; unsigned long myfunc(void) { return 0; } main() { setCallback((void*)myfunc); gfnPtr(); /* Crashing as value was not properly assigned in setCallback function */ } 

Qui gfnPtr () si blocca su 64-bit suse linux quando compilato con gcc. Ma chiama con successo gfnPtr () VC6 e SunOS.

Ma se cambio la funzione come indicato di seguito, funziona correttamente.

 void setCallback(const void *fnPointer) { int i; // put any statement here gfnPtr = *((FN_GET_VAL*) (&fnPointer)); } 

Si prega di aiutare con la causa del problema. Grazie.

Lo standard non consente di castare i puntatori di funzioni per void* . È ansible eseguire il cast solo su un altro tipo di puntatore di funzione. 6.3.2.3 §8:

Un puntatore a una funzione di un tipo può essere convertito in un puntatore a una funzione di un altro tipo e viceversa

È importante ricordare che è necessario tornare al tipo originale prima di utilizzare il puntatore per chiamare la funzione (tecnicamente, per un tipo compatibile. Definizione di “compatibile” in 6.2.7).

Lo standard purtroppo non consente il cast tra puntatori di dati e puntatori di funzioni (perché ciò potrebbe non avere senso su alcune piattaforms davvero oscure), anche se POSIX e altri richiedono tali lanci. Una soluzione alternativa è non lanciare il puntatore ma lanciare un puntatore al puntatore (questo è OK dal compilatore e farà il lavoro su tutte le normali piattaforms).

 typedef void (*FPtr)(void); // Hide the ugliness FPtr f = someFunc; // Function pointer to convert void* ptr = *(void**)(&f); // Data pointer FPtr f2 = *(FPtr*)(&ptr); // Function pointer restored 

Ho tre regole empiriche quando si tratta di puntatori di dati e puntatori di codice:

  • Non combinare puntatori di dati e puntatori di codice
  • Non combinare puntatori di dati e puntatori di codice
  • Non mischiare mai puntatori di dati e puntatori di codice!

Nella seguente funzione:

 void setCallback(const void *fnPointer) { gfnPtr = *((FN_GET_VAL*) (&fnPointer)); } 

Si dispone di un puntatore di dati che si applica a un puntatore di funzione. (Senza contare che lo fai prendendo prima l’indirizzo del puntatore stesso, gettalo su un puntatore a un puntatore, prima di de-referenziarlo).

Prova a riscriverlo come:

 void setCallback(FN_GET_VAL fnPointer) { gfnPtr = fnPointer; } 

Inoltre, puoi (o dovresti) rilasciare il cast quando imposti il ​​puntatore:

 main() { setCallback(myfunc); gfnPtr(); } 

Come bonus extra, ora puoi utilizzare i normali controlli di tipo eseguiti dal compilatore.

Suggerirò una ansible spiegazione parziale .

@Manoj Se si esamina (o può fornire) l’elenco di assembly per SetCallback generato da entrambi i compilatori, è ansible ottenere una risposta definitiva.

In primo luogo, le affermazioni di Pascal Couq sono corrette e Lindydancer mostra come impostare correttamente il callback. La mia risposta è solo un tentativo di spiegare il problema reale.

Penso che il problema derivi dal fatto che Linux e l’altra piattaforma usano diversi modelli a 64 bit (vedi modelli a 64 bit su Wikipedia ). Nota che Linux usa LP64 (int è a 32 bit). Abbiamo bisogno di maggiori dettagli sull’altra piattaforma. Se è SPARC64, utilizza ILP64 (int è 64 bit).

Come ho capito, il problema è stato osservato solo su Linux e se ne è stata introdotta una variabile locale int. Hai provato questo con ottimizzazioni off o on? Molto probabilmente questo hack non avrebbe alcun effetto benefico con le ottimizzazioni.

In entrambi i modelli a 64 bit, i puntatori devono essere a 64 bit, indipendentemente dal fatto che puntino a codice o dati. Tuttavia, è ansible che questo non sia il caso (ad esempio modelli di memoria segmentata); quindi, le ammonizioni di Pascal e Lindydancer.

Se i puntatori hanno le stesse dimensioni, rimane un ansible problema di allineamento dello stack. L’introduzione di un int locale (che è a 32 bit sotto Linux) potrebbe alterare l’allineamento. Questo avrebbe effetto solo se i puntatori void * e funzione hanno requisiti di allineamento diversi. Uno scenario dubbio.

Tuttavia, i diversi modelli di memoria a 64 bit sono molto probabilmente la causa di ciò che hai osservato. Siete invitati a fornire gli elenchi dell’assembly in modo che possiamo analizzarli.

a differenza di ciò che dicono gli altri, sì, si può avere un puntatore void* come puntatore a funzione, ma la semantica è molto difficile da usare con.

Come puoi vedere, non hai BISOGNO di scriverlo come un void* , basta assegnarlo come al solito. Ho eseguito il tuo codice in questo modo e l’ho modificato per funzionare.

file1.c:

 typedef unsigned long (*FN_GET_VAL)(void); extern FN_GET_VAL gfnPtr; void setCallback(const void *fnPointer) { gfnPtr = ((FN_GET_VAL) fnPointer); } 

file2.c:

 int main(void) { setCallback(myfunc); (( unsigned long(*)(void) )gfnPtr)(); }