En ce qui concerne la norme C, si vous transformez un pointeur de fonction en un pointeur de fonction d'un type différent et que vous l'appelez ensuite, il s'agit de comportement indéfini . Voir l'annexe J.2 (informative) :
Le comportement est indéfini dans les circonstances suivantes :
- Un pointeur est utilisé pour appeler une fonction dont le type n'est pas compatible avec le type pointé (6.3.2.3). pointé (6.3.2.3).
Le paragraphe 8 de la section 6.3.2.3 se lit comme suit :
Un pointeur vers une fonction d'un type peut être converti en un pointeur vers une fonction d'un autre type et inversement. type et inversement ; le résultat doit être égal au pointeur original. Si un pointeur converti est utilisé pour appeler une fonction dont le type n'est pas compatible avec le type pointé, le comportement est indéfini.
En d'autres termes, vous pouvez transférer un pointeur de fonction vers un autre type de pointeur de fonction, le retransférer et l'appeler, et tout fonctionnera.
La définition de compatible est quelque peu compliqué. Elle se trouve à la section 6.7.5.3, paragraphe 15 :
Pour que deux types de fonctions soient compatibles, les deux doivent spécifier des types de retour compatibles. 127 .
En outre, les listes de types de paramètres, si elles sont toutes deux présentes, doivent concorder quant au nombre de paramètres et à l'utilisation de ces paramètres. paramètres et dans l'utilisation de la terminaison des points de suspension ; les paramètres correspondants doivent avoir des types types compatibles. Si un type possède une liste de types de paramètres et que l'autre type est spécifié par un déclarateur de fonction qui ne fait pas partie d'une définition de fonction et qui contient une liste d'identificateurs vide, ce type de déclaration n'est pas compatible avec le type de paramètre. vide, la liste des paramètres ne doit pas comporter de point de suspension et le type de chaque paramètre doit être compatible avec le type de la fonction. paramètre doit être compatible avec le type résultant de l'application des promotions d'arguments par défaut. promotions d'arguments par défaut. Si un type possède une liste de types de paramètres et que l'autre type est spécifié par une définition de fonction qui contient une liste d'identificateurs (éventuellement vide), les deux doivent s'accorder sur le nombre de paramètres, et le type de chaque paramètre prototype doit être compatible avec le type résultant de l'application des promotions d'arguments par défaut. compatible avec le type qui résulte de l'application de l'argument par défaut au type de l'identificateur correspondant. (Pour déterminer la compatibilité des types et d'un type composite, chaque paramètre déclaré avec un type de fonction ou de tableau est considéré comme ayant le type ajusté. est considéré comme ayant le type ajusté et chaque paramètre déclaré avec le type qualifié est considéré comme ayant la version non qualifiée de son type déclaré).
127) Si les deux types de fonctions sont de type "ancien", les types de paramètres ne sont pas comparés.
Les règles permettant de déterminer si deux types sont compatibles sont décrites dans la section 6.2.7, et je ne les citerai pas ici car elles sont assez longues, mais vous pouvez les lire sur le site de l projet de la norme C99 (PDF) .
La règle pertinente ici se trouve à la section 6.7.5.1, paragraphe 2 :
Pour que deux types de pointeurs soient compatibles, les deux doivent être qualifiés de manière identique et les deux doivent être des pointeurs vers des types compatibles.
Par conséquent, puisque a void*
n'est pas compatible avec un struct my_struct*
un pointeur de fonction de type void (*)(void*)
n'est pas compatible avec un pointeur de fonction de type void (*)(struct my_struct*)
Ce casting de pointeurs de fonction est donc techniquement un comportement non défini.
En pratique, cependant, vous pouvez sans risque vous passer du casting des pointeurs de fonction dans certains cas. Dans la convention d'appel x86, les arguments sont poussés sur la pile, et tous les pointeurs ont la même taille (4 octets en x86 ou 8 octets en x86_64). L'appel d'un pointeur de fonction se résume à pousser les arguments sur la pile et à effectuer un saut indirect vers la cible du pointeur de fonction, et il n'y a évidemment aucune notion de type au niveau du code machine.
Les choses que vous avez définitivement ne peut pas faire :
- Cast entre les pointeurs de fonction de différentes conventions d'appel. Vous allez perturber la pile et, au mieux, vous planter, au pire, réussir en silence avec une énorme faille de sécurité béante. Dans la programmation Windows, vous faites souvent circuler des pointeurs de fonction. Win32 s'attend à ce que toutes les fonctions de rappel utilisent l'attribut
stdcall
convention d'appel (que les macros CALLBACK
, PASCAL
y WINAPI
se développent toutes). Si vous passez un pointeur de fonction qui utilise la convention d'appel standard du C ( cdecl
), il en résultera de la méchanceté.
- En C++, il faut faire la distinction entre les pointeurs de fonctions de membres de classe et les pointeurs de fonctions ordinaires. Cela fait souvent trébucher les débutants en C++. Les fonctions membres d'une classe ont une fonction cachée
this
et si vous transformez une fonction membre en une fonction normale, il n'y a pas de paramètre this
objet à utiliser, et encore une fois, beaucoup de mal en résultera.
Une autre mauvaise idée qui peut parfois fonctionner mais qui est aussi un comportement indéfini :
- Le transfert entre les pointeurs de fonction et les pointeurs normaux (par exemple, le transfert d'un fichier
void (*)(void)
à un void*
). Les pointeurs de fonction n'ont pas nécessairement la même taille que les pointeurs ordinaires, car sur certaines architectures, ils peuvent contenir des informations contextuelles supplémentaires. Cela fonctionnera probablement bien sur x86, mais rappelez-vous que c'est un comportement non défini.
2 votes
Je suis un peu novice, mais qu'est-ce qu'un "void ( )(void ) pointeur de fonction" signifie ? Est-ce un pointeur vers une fonction qui accepte un void* en tant qu'argument et renvoie void
2 votes
@Myke :
void (*func)(void *)
signifie quefunc
est un pointeur vers une fonction avec une signature de type telle quevoid foo(void *arg)
. Alors oui, vous avez raison.