2017-07-26 2 views
6

Le code suivant est un exemple de la C11 standard §6.5.2.3:C11 liées à la correction de la langue

struct t1 { int m; }; 
struct t2 { int m; }; 
int f(struct t1 *p1, struct t2 *p2) 
{ 
    if (p1->m < 0) 
     p2->m = -p2->m; 
    return p1->m; 
} 
int g() 
{ 
    union { 
     struct t1 s1; 
     struct t2 s2; 
    } u; 
    /* ... */ 
    return f(&u.s1, &u.s2); 
} 

Comme par C11, la dernière ligne à l'intérieur g() est invalide. Pourquoi ça?

+2

Quelle est l'erreur réelle? – MrJLP

+0

... et, tout à fait tangentiellement, je note que ce fragment définit 'g' comme' int g() 'plutôt que' int g (void) '. Cela devrait mettre au repos le canard que 'int main()' n'est pas approuvé par la norme - il n'y a pas de prototype pour 'g()', mais la définition de la fonction est valide. –

+2

J'ai lu le problème car la fonction 'g' passe dans les deux champs de l'union à' f'. 'f' traite chaque paramètre comme une structure indépendante, sauf qu'ils proviennent réellement de la même union et donc de l'alias. – Brian

Répondre

6

L'exemple provient de l'exemple 3 du §6.5.2.3 Membres de structure et d'union de l'ISO/CEI 9899: 2011. L'un des paragraphes précédents est (accent ajouté):

¶6 une garantie spéciale est faite afin de simplifier l'utilisation des syndicats: si un syndicat contient plusieurs structures qui partagent une séquence initiale commune (voir ci-dessous), et si l'objet union contient actuellement l'une de ces structures, il est permis d'inspecter la partie initiale commune de l'une d'entre elles partout où une déclaration du type complété de l'union est visible. Deux structures partagent une séquence initiale commune si les membres correspondants ont des types compatibles (et, pour les bits-champs, les mêmes largeurs) pour une séquence d'un ou plusieurs membres initiaux.

Le code cité dans la question est précédée du commentaire:

Ce qui suit est pas un fragment valide (parce que le type d'union ne sont pas visibles dans la fonction f).

Cela a maintenant du sens à la lumière de l'instruction en surbrillance. Le code g() utilise la séquence initiale commune, mais cela s'applique uniquement lorsque le union est visible et qu'il n'est pas visible dans f().

Le problème est également celui de l'aliasing strict. C'est un sujet complexe. Voir What is the strict aliasing rule? pour les détails.

Pour tout ce que cela vaut, GCC 7.1.0 ne signale pas le problème, même sous des options d'avertissement rigoureuses. Ni ne Clang, même avec l'option -Weverything:

clang -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \ 
    -Wstrict-prototypes -Weverything -pedantic … 
+0

J'essaie toujours de le comprendre: '& u.s1' et' & u.s2' sont évalués à l'intérieur de g (et g a la visibilité de union u) avant d'appeler f, puis ils sont passés comme une paire de paramètres d'entrée (un pointeur struct t1 et un pointeur vers astruct t2) à f sur stack. Pourquoi f a besoin de connaître les détails de l'union? –

+0

Bonne question. Je n'ai pas de réponse définitive - mais c'est à peu près "parce que la norme le dit". Deux compilateurs C importants ne pensent pas qu'il y a un problème, même quand leurs bras sont tordus pour se plaindre de tout et de rien. J'ai revérifié avec '-fstrict-aliasing' spécifié aussi; aucune plainte. Dans mon commentaire initial, j'ai noté que les exemples ne sont pas normatifs - ils peuvent être enlevés sans changer la signification de la norme, et les erreurs dans les exemples sont indésirables (et rares) mais n'affectent pas la validité de la norme (voir [SO 21364398]) (https://stackoverflow.com/questions/21364398/)). –

+0

Le sous-texte à mon commentaire est qu'il y a une possibilité que cet exemple est erroné, même si c'est peu probable. Vous pouvez trouver des règles sur le crénelage dans la norme C11 §6.5 ** Expressions **, ¶7: Un objet doit avoir sa valeur stockée accessible uniquement par une expression lvalue qui a l'un des types suivants: _ (suivi d'une liste à puces) avec la note de bas de page 88 qui dit _L'objectif de cette liste est de spécifier les circonstances dans lesquelles un objet peut ou non être aliasé._ La liste utilise le terme _effective type_ spécifié dans ¶6 - voir aussi §6.2.7 ** Type compatible et type composite **. est également –

3

Ceci est dû à la règle "de type efficace". Si vous voyez f isolé, les deux arguments ont un type différent et le compilateur est autorisé à effectuer certaines optimisations.

Ici, p1 est accédé deux fois. Si p1 et p2 sont supposés être différents, le compilateur n'a pas besoin de recharger la valeur de p1 pour return puisqu'il ne peut pas avoir changé.

f est un code valide, et l'optimisation est valide.

L'appeler avec le même objet, dans g, n'est pas valide, car sans voir que les deux peuvent provenir du même union le compilateur peut ne pas prendre de dispositions pour éviter l'optimisation. Ceci est l'un des cas, où toute la charge de prouver qu'un appel est valide repose sur l'utilisateur d'une fonction, généralement aucun compilateur ne peut vous avertir à ce sujet si f et g se trouvent dans différentes unités de traduction.

+0

La règle Effective Type offre aux objets heap-duration un exemple d'objets qui n'ont pas de type déclaré, et ne contient pas de règle pour le traitement des objets de durée statique ou automatique (qui doivent avoir des types déclarés) bien qu'ils aient un type déclaré dans certains contextes mais pas d'autres. Exiger que les compilateurs traitent de tels objets comme s'ils pouvaient avoir un type déclaré que le compilateur ne voit pas entraverait beaucoup l'optimisation, mais je ne connais rien dans la norme qui dise que les compilateurs doivent seulement suivre les règles ... – supercat

+0

.. .when pratique pour le faire. Qu'est-ce que je rate? – supercat