Pointeur de fonction converti en une signature différente
Je utiliser une structure de pointeurs de fonction à implémenter une interface pour les différents backends. Les signatures sont très différents, mais les valeurs de retour sont presque tous void, void * ou int.
struct my_interface {
void (*func_a)(int i);
void *(*func_b)(const char *bla);
...
int (*func_z)(char foo);
};
Mais il n'est pas nécessaire qu'un backends prend en charge les fonctions pour chaque fonction d'interface. J'ai donc deux possibilités, la première option est de vérifier avant chaque appel, si le pointeur est inégale valeur NULL. Je n'aime pas beaucoup parce que de la lisibilité et parce que j'ai peur de l'impact sur les performances (je n'ai pas mesuré, cependant). L'autre option est d'avoir une fonction factice, pour les rares cas une fonction d'interface n'existe pas.
Donc j'aurais besoin d'une fonction factice pour chaque signature, je me demande si il est possible d'avoir un seul pour les différentes valeurs de retour. Et il la jeta à la signature.
#include <stdio.h>
int nothing(void) {return 0;}
typedef int (*cb_t)(int);
int main(void)
{
cb_t func;
int i;
func = (cb_t) nothing;
i = func(1);
printf("%d\n", i);
return 0;
}
J'ai testé ce code avec gcc et il fonctionne. Mais est-il sain d'esprit? Ou peut-il endommager la pile ou il peut causer d'autres problèmes?
EDIT: Merci pour toutes les réponses, j'ai appris aujourd'hui beaucoup sur les conventions d'appel, après un peu de lecture. Et nous avons maintenant une bien meilleure compréhension de ce qui se passe sous le capot.
source d'informationauteur quinmars
Vous devez vous connecter pour publier un commentaire.
Par le C de la spécification, de la coulée d'un pointeur de fonction entraîne un comportement non défini. En fait, pendant un certain temps, GCC 4.3 prereleases serait de retourner la valeur NULL à chaque fois que vous avez coulé un pointeur de fonction, parfaitement valide par la spec, mais ils ont soutenu que le changement avant la libération, parce qu'il a cassé beaucoup de programmes.
En supposant que GCC continue de faire ce qu'il fait maintenant, cela fonctionnera très bien avec la valeur par défaut x86 convention d'appel (et la plupart des conventions d'appel sur la plupart des architectures), mais je ne voudrais pas compter sur elle. Les tests de la fonction pointeur par rapport à NULL à chaque callsite n'est pas beaucoup plus cher qu'un appel de fonction. Si vous voulez vraiment, vous pouvez écrire une macro:
Ou vous pourriez avoir une autre fonction factice pour chaque signature, mais je peux comprendre que vous voulez éviter.
Modifier
Charles Bailey m'a appelé sur ce, j'ai donc étudié les détails (au lieu de compter sur mon holey de mémoire). Le C les spécifications dit
et GCC 4.2 prereleases (cela a été réglée de façon avant 4.3) a la suite de ces règles: le casting d'un pointeur de fonction n'a pas de résultat NUL, comme je l'ai écrit, mais essayez d'appeler une fonction par le biais d'un incompatible type, c'est à dire
à partir de votre exemple, entraînerait une
abort
. Ils ont changé de retour à la 4.1 comportement (permettre, mais avertir), en partie parce que ce changement a cassé OpenSSL, mais OpenSSL a été fixé dans le temps, et c'est un comportement indéfini qui le compilateur est libre de modifier à tout moment.OpenSSL a été coulée seulement des pointeurs de fonctions à d'autres types de fonction de prise et de retour le même nombre de valeurs de la même taille exacte, et cela (en supposant que vous n'avez pas affaire à virgule flottante) se trouve être sûr à travers toutes les plates-formes et des conventions d'appel que je connais. Cependant, tout le reste est potentiellement dangereux.
Je soupçonne que vous obtiendrez un comportement indéfini.
Vous pouvez attribuer (avec le bon cast) un pointeur de fonction à un autre pointeur de fonction avec une signature différente, mais quand vous l'appelez des choses étranges peuvent se produire.
Votre
nothing()
fonction ne prend pas d'arguments, pour le compilateur, cela peut signifier qu'il est possible d'optimiser l'utilisation de la pile comme il n'y aura pas des arguments. Mais ici, vous l'appelez avec un argument, c'est une situation inattendue et il peut tomber en panne.Je ne peux pas trouver le bon point dans la norme, mais je me souviens qu'il dit que vous pouvez lancer des pointeurs de fonction, mais lorsque vous appelez la fonction obtenue que vous avez à faire avec le droit prototype sinon le comportement est indéfini.
Comme une note de côté, vous ne devriez pas comparer un pointeur de fonction avec un pointeur de données (comme NULL) comme toi pointeurs peuvent appartenir à des espaces d'adressage différents. Il y a une annexe dans le standard C99 qui permet à ce cas spécifique, mais je ne pense pas que c'est largement mis en œuvre. Cela dit, sur l'architecture, où il y a un seul espace d'adressage de la coulée d'un pointeur de fonction à un pointeur de données ou en les comparant avec la valeur NULL, sera habituellement.
Vous courez le risque de provoquer une corruption de la pile. Cela dit, si vous déclarez les fonctions avec
extern "C"
de liaison (et/ou__cdecl
selon votre compilateur), vous peut être en mesure de s'en tirer avec cela. Il serait semblable alors à la manière d'une fonction commeprintf()
peut prendre un nombre variable d'arguments lors de l'appelant discrétion.De savoir si cela fonctionne ou pas dans votre situation actuelle peut aussi dépendre de l'exacte des options du compilateur que vous utilisez. Si vous êtes à l'aide de MSVC, puis debug contre la libération d'options de compilation peut faire une grande différence.
Il doit être fine. Depuis l'appelant est responsable du nettoyage de la pile après un appel, il ne devrait pas laisser grand chose de plus sur la pile. Le destinataire de l'appel (rien() dans ce cas) est ok car il l'habitude d'essayer d'utiliser tous les paramètres sur la pile.
EDIT: cela ne suppose cdecl conventions d'appel, qui est habituellement la valeur par défaut pour C.
Aussi longtemps que vous pouvez garantir que vous faites un appel à l'aide d'une méthode qui a l'appelant de l'équilibre de la pile plutôt que le destinataire de l'appel (__cdecl). Si vous n'avez pas de convention d'appel spécifié la convention mondiale pourrait être autre chose. (__stdcall ou __fastcall) à la Fois de ce qui pourrait conduire à une corruption de la pile.
Cela ne fonctionnera pas, sauf si vous utilisez la mise en œuvre spécifique/plate-forme spécifique des choses à force de les corriger convention d'appel. Pour certaines conventions d'appel de la fonction appelée est responsable du nettoyage de la pile, de sorte qu'ils doivent savoir ce qui s'est poussé sur.
J'irais pour le vérifier la valeur NULL alors appelez - je ne peux pas imaginer qu'il aurait une incidence sur les performances.
Les ordinateurs peuvent vérifier la valeur NULL à peu près aussi rapide que tout ce qu'ils font.
Casting d'un pointeur de fonction à la valeur NULL est pas explicitement pris en charge par la norme. Vous êtes à la merci du compilateur écrivain. Il fonctionne bien sur un grand nombre de compilateurs.
Il est l'un des grands inconvénients de C qu'il n'existe pas d'équivalent de la valeur NULL ou vide* pour les pointeurs de fonction.
Si vous voulez vraiment que votre code à l'épreuve des balles, vous pouvez déclarer vos propres valeurs null, mais vous avez besoin d'un pour chaque type de fonction. Par exemple,
et ensuite vous pouvez tester
Laid, pas vrai?