6
votes

En passant `int (*) (chartons *)` Où `int (*) (char *)` est attendu

J'ai un pointeur de fonction dont la fonction est déclarée comme espérant char * code> arguments.int, je voudrais enregistrer un pointeur sur une fonction déclarée comme prenant Char Cons * Code> Arguments.

Je suppose que je peux utiliser une enveloppe ou une coulée. Les moulages semblent plus simples, mais puis-je appeler légalement le résultat d'un tel pointeur de fonction Cast? P>

Exemple de code ci-dessous: P>

static int write_a(char * X){
    return 0;
}

static int write_b(char const* X){
    return 0;
}
static int wrapped_write_b(char * X){
    return write_b(X);
}

typedef int (*write_fn)(char * );

write_fn a = write_a;
write_fn b = wrapped_write_b;
write_fn b1 = (write_fn)write_b; //is b1 legally callable?


2 commentaires

Selon Ce c'est UB (je pense). Toutefois, comme Const n'est appliqué que dans le temps de construction, pas de temps d'exécution, je ne pense pas que ce sera un problème.


@DANH: La distribution est parfaitement légale. Le lien que vous avez fourni n'est pas lié à cette distribution du tout.


3 Réponses :


10
votes

Ceci est un comportement non défini - vous pouvez utiliser un pointeur pour appeler une fonction d'un autre type uniquement si les types sont compatibles ( 6.3.2.3 / 8 ):

Un pointeur sur une fonction d'un type peut être converti en un pointeur sur une fonction d'un autre type et de retour à nouveau; Le résultat doit comparer égal au pointeur d'origine. Si un pointeur converti est utilisé pour appeler une fonction dont le type n'est pas compatible avec le type référencé, le comportement est indéfini.

Deux fonctions ont des types compatibles si (version simplifiée), ils ont le même retour et les mêmes arguments sont compatibles ( 6.7.6.3 , sémantique / 15 ):

Pour deux types de fonctions à compatibilité, les deux doivent spécifier des types de rendement compatibles.146) De plus, les listes de types de paramètres, si elles sont toutes deux présentes sont d'accord sur le nombre de paramètres et à l'utilisation du terminateur ellipsis; Les paramètres correspondants doivent avoir des types compatibles.

A const caractère * n'est pas compatible avec un char * ( 6.7.6.1 , Sémantique / 2 ):

Pour deux types de pointeur compatibles, les deux doivent être qualifiés de manière identique et les deux doivent être des pointeurs à des types compatibles.

Etant donné que const char * et Char * ne sont pas qualifiés de manière identique, ils ne sont pas compatibles, et appelant write_b via B < / code> est un comportement indéfini.


2 commentaires

Aucun problème. Merci.


"Vous pouvez utiliser un pointeur pour appeler une fonction ..." Mais le code de l'OP n'est pas appeler la fonction via b1 !



3
votes

Strictement parler, il n'est pas permis.

A pointeur-à-quelque chose n'est pas compatible avec un pointeur-à-qualifié-quelque chose . Parce qu'un pointeur-à-qualifié-quelque chose n'est pas un type qualifié de pointeur-à-quelque chose

La même chose s'applique à

pointeur-to-fonction-accepter-quelque chose

et

Pointeur-to-fonction-acceptation-QUALIFIÉ-QUOI .

Ceci peut être trouvé via C11 6.2.7 Type compatible:

Deux types ont un type compatible si leurs types sont les mêmes. Règles supplémentaires pour déterminer si deux types sont compatibles sont compatibles décrit dans 6.7.2 pour les spécificateurs de type, dans < un href = "http://port70.net/~nsz/c/c11/n1570.html#6.7.3" rel = "NOFOollow NOREFERRER"> 6.7.3 pour type Qualifiers ...

6.7.3 est la partie pertinente . Il dit

Pour deux types qualifiés à compatibilité, les deux doivent avoir la version qualifiée identique d'un type compatible;

Le chapitre de conversion 6.3.2.3 ne fait pas contredire ceci:

Un pointeur sur une fonction d'un type peut être converti en un pointeur sur une fonction d'une autre Tapez et retour; Le résultat doit comparer égal au pointeur d'origine. si un converti le pointeur est utilisé pour appeler une fonction dont le type n'est pas compatible avec le type référencé, Le comportement est indéfini.

Modifier

Comme indiqué dans la réponse par Holt, la compatibilité de deux fonctions est explicitement décrite dans 6.7.6.3/15 .


Je pense toujours qu'une fonction d'emballage est la meilleure solution. La racine du problème est que wreck_a n'est pas Const-correct. Si vous ne pouvez pas modifier cette fonction, écrivez-le.


3 commentaires

Le casting lui-même est parfaitement autorisé et légal. Qu'est-ce que "non autorisé" appelle la fonction via b1 . Mais le code de l'OP ne apporte aucun appels via b1 .


En outre, la remarque "stricte aliasing" n'est pas pertinente ici. Les concepts de aliasing (et aliasing strict strict ) ne sont applicables qu'aux pointeurs à des données, non aux indications des fonctions.


@Ant sur votre première remarque, c'est exactement ce que dit le texte en gras. Vous êtes correct sur l'aliasing strict, la norme ne mentionne que des objets. Fixé.



2
votes

write_fn b1 = (write_fn) write_b; // est ce légal?

est quel légal?

Les types de pointeur de fonction impliqués dans cette distribution ne sont pas compatibles.

Le pointeur de fonction de casting sur un type de pointeur de fonction incompatible est parfaitement légal. Cependant, la seule chose que vous puissiez faire avec un tel pointeur converti de force est le reconvertir au type d'origine. La spécification linguistique garantit qu'une telle conversion de voyage aller-retour préserve la valeur du pointeur d'origine. C'est pourquoi nous pouvons utiliser, disons, void (*) (void) en tant que "type de stockage universel" pour les pointeurs de fonction (comme VOID * pour les pointeurs de données). Il peut être utilisé pour stocker les pointeurs de fonction de tout type (mais pas pour appeler les fonctions). Pour effectuer un tel stockage de pointeur (et une récupération), nous devrons utiliser des counts explicites, comme celui de votre code. Il n'y a rien d'illégal à ce sujet.

Pendant ce temps, essayez d'appeler la fonction via B1 entraînera un comportement non défini, en particulier parce que le type de pointeur n'est pas compatible avec le type de fonction réel.

Dans votre question, vous indiquez clairement que vous souhaitez "enregistrer" le pointeur sur cette fonction. Tant que nous ne parlons que de "enregistrer" (stocker) le pointeur, votre code est parfaitement sans faille.


2 commentaires

Bon point. Appeler la fonction à travers ce pointeur était un objectif que j'aurais dû expliquer explicitement.


Suggérez l'ajout de ref: C11 §6.3.2.3 8 "Un pointeur sur une fonction d'un type peut être converti en un pointeur sur une fonction d'un autre type et de retour à nouveau; le résultat doit comparer égal au pointeur d'origine."