Per quanto riguarda lo standard C, se si lancia un puntatore a funzione su un puntatore a funzione di un tipo diverso e poi lo si chiama, si tratta di un comportamento indefinito . Vedi Allegato J.2 (informativo):
Il comportamento non è definito nelle seguenti circostanze:
- Un puntatore viene utilizzato per chiamare una funzione il cui tipo non è compatibile con il tipo puntato (6.3.2.3).
La sezione 6.3.2.3, paragrafo 8 recita:
Un puntatore a una funzione di un tipo può essere convertito in un puntatore a una funzione di un altro tipo e viceversa; il risultato deve essere paragonato al puntatore originale. Se un puntatore convertito viene utilizzato per chiamare una funzione il cui tipo non è compatibile con il tipo a cui punta, il comportamento è indefinito.
Quindi, in altre parole, puoi eseguire il cast di un puntatore a funzione su un diverso tipo di puntatore a funzione, eseguirne nuovamente il cast e richiamarlo e le cose funzioneranno.
La definizione di compatibile è alquanto complicata. Può essere trovato nella sezione 6.7.5.3, paragrafo 15:
Affinché due tipi di funzioni siano compatibili, entrambi devono specificare i tipi restituiti compatibili 127 .
Inoltre, gli elenchi dei tipi di parametro, se presenti entrambi, dovranno concordare nel numero dei parametri e nell'uso dei puntini di sospensione; i parametri corrispondenti devono avere tipi compatibili. Se un tipo ha un elenco di tipi di parametro e l'altro tipo è specificato da un dichiaratore di funzione che non fa parte di una definizione di funzione e che contiene un elenco di identificatori vuoto, l'elenco di parametri non deve avere un terminatore con puntini di sospensione e il tipo di ciascun parametro deve essere compatibile con il tipo che risulta dall'applicazione delle promozioni degli argomenti predefiniti. Se un tipo ha un elenco di tipi di parametri e l'altro tipo è specificato da una definizione di funzione che contiene un elenco di identificatori (possibilmente vuoto), entrambi devono concordare sul numero di parametri, e il tipo di ogni parametro del prototipo deve essere compatibile con il tipo che risulta dall'applicazione delle promozioni degli argomenti predefiniti al tipo dell'identificatore corrispondente. (Nella determinazione della compatibilità del tipo e di un tipo composto, ogni parametro dichiarato con funzione o tipo di matrice è considerato come avente il tipo corretto e ogni parametro dichiarato con tipo qualificato è considerato avente la versione non qualificata del suo tipo dichiarato.)
127) Se entrambi i tipi di funzione sono "vecchio stile", i tipi di parametro non vengono confrontati.
Le regole per determinare se due tipi sono compatibili sono descritte nella sezione 6.2.7, e non le citerò qui poiché sono piuttosto lunghe, ma puoi leggerle sulla bozza dello standard C99 (PDF) .
La regola pertinente qui è nella sezione 6.7.5.1, paragrafo 2:
Affinché due tipi di puntatori siano compatibili, entrambi devono essere qualificati in modo identico ed entrambi devono essere puntatori a tipi compatibili.
Quindi, poiché a void*
non è compatibile con a struct my_struct*
, un puntatore a funzione di tipo void (*)(void*)
non è compatibile con un puntatore a funzione di tipo void (*)(struct my_struct*)
, quindi questo casting di puntatori a funzione è un comportamento tecnicamente indefinito.
In pratica, tuttavia, in alcuni casi puoi tranquillamente farla franca con il casting di puntatori a funzione. Nella convenzione di chiamata x86, gli argomenti vengono inseriti nello stack e tutti i puntatori hanno la stessa dimensione (4 byte in x86 o 8 byte in x86_64). Chiamare un puntatore a funzione si riduce a spingere gli argomenti sullo stack e fare un salto indiretto alla destinazione del puntatore a funzione, e ovviamente non c'è nozione di tipi a livello di codice macchina.
Cose che sicuramente non puoi fare:
- Cast tra puntatori a funzione di diverse convenzioni di chiamata. Incasinerai lo stack e nel migliore dei casi, andrai in crash, nel peggiore dei casi, riuscirai silenziosamente con un enorme buco di sicurezza aperto. Nella programmazione Windows, si passano spesso i puntatori alle funzioni. Win32 si aspetta che tutte le funzioni di callback per utilizzare la
stdcall
convenzione di chiamata (che le macro CALLBACK
, PASCAL
e WINAPI
tutto si espandono a). Se si passa un puntatore a funzione che utilizza la convenzione di chiamata C standard ( cdecl
), ne risulterà una cattiveria.
- In C ++, eseguire il cast tra puntatori a funzione membro di classe e puntatori a funzione regolare. Questo spesso inciampa i neofiti del C ++. Le funzioni membro della classe hanno un
this
parametro nascosto e se si esegue il cast di una funzione membro in una funzione normale, non ci sono this
oggetti da usare e, di nuovo, ne risulterà molta cattiveria.
Un'altra pessima idea che a volte potrebbe funzionare ma è anche un comportamento indefinito:
- Casting tra puntatori a funzione e puntatori regolari (ad esempio casting da a
void (*)(void)
ad a void*
). I puntatori a funzione non hanno necessariamente le stesse dimensioni dei puntatori regolari, poiché su alcune architetture potrebbero contenere informazioni contestuali extra. Questo probabilmente funzionerà bene su x86, ma ricorda che è un comportamento indefinito.