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
stdcallconvenzione di chiamata (che le macro CALLBACK, PASCALe WINAPItutto 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
thisparametro nascosto e se si esegue il cast di una funzione membro in una funzione normale, non ci sono thisoggetti 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.