31 votes

La règle d'alias stricte est-elle spécifiée de manière incorrecte?

Comme établi précédemment, une union de la forme

union some_union {
    type_a member_a;
    type_b member_b;
    ...
};

avec n membres comporte n + 1 objets qui se chevauchent de stockage: Un objet pour l'union elle-même et un objet pour chaque membre de l'union. Il est clair, que vous pouvez librement lire et à écrire aux membres de l'union dans n'importe quel ordre, même si la lecture d'un membre de l'union qui n'était pas le dernier écrit à. La stricte aliasing règle n'est jamais violé, car la lvalue par laquelle vous accédez à la de stockage est correct efficace.

C'est plus pris en charge par la note de bas de page 95, qui explique comment le type de beaucoup les jeux de mots est un usage de syndicats.

Un exemple typique de la optimisations activées par la stricte aliasing règle est cette fonction:

int strict_aliasing_example(int *i, float *f)
{
    *i = 1;
    *f = 1.0;
    return (*i);
}

le compilateur peut optimiser à quelque chose comme

int strict_aliasing_example(int *i, float *f)
{
    *i = 1;
    *f = 1.0;
    return (1);
}

car on peut supposer que l'écriture d' *f n'affecte pas la valeur de *i.

Cependant, ce qui arrive quand on passe deux pointeurs vers des membres de la même union? Considérons cet exemple, en supposant un typique de la plate-forme où float est une norme IEEE 754 simple précision à virgule flottante nombre et int est un 32 bits en complément à deux entiers:

int breaking_example(void)
{
    union {
        int i;
        float f;
    } fi;

    return (strict_aliasing_example(&fi.i, &fi.f));
}

Comme établi précédemment, fi.i et fi.f se référer à un chevauchement de mémoire de la région. De la lecture et de l'écriture est sans condition juridique (l'écriture n'est légale que lorsque l'union a été initialisé) dans n'importe quel ordre. À mon avis, précédemment évoquées optimisation effectuée par tous les principaux compilateurs rendements de code incorrect que les deux pointeurs de type différent légalement pointer vers le même emplacement.

J'ai en quelque sorte ne peux pas croire que mon interprétation de la stricte aliasing règle est correcte. Il ne semble pas plausible que l'optimisation de la stricte aliasing a été conçu pour n'est pas possible en raison de ces cas de coin.

S'il vous plaît dites-moi pourquoi je suis mal.

Une question connexe tourné au cours de la recherche.

Veuillez lire toutes les réponses existantes et de leurs observations avant l'ajout de votre propre, assurez-vous que votre réponse ajoute un nouvel argument.

17voto

davmac Points 4317

Départ avec votre exemple:

int strict_aliasing_example(int *i, float *f)
{
    *i = 1;
    *f = 1.0;
    return (*i);
}

Nous allons d'abord reconnaître que, en l'absence de syndicats, ce serait violer le strict aliasing règle s' i et f pointent vers le même objet; en supposant que l'objet n'est pas efficace, alors *i = 1 jeux de l'effectif type d' int et *f = 1.0 lui float, et la finale, return (*i) puis accède à un objet avec efficace d' float via une lvalue de type int, ce qui est clairement interdit.

La question est de savoir si cela serait tout de même montant à un strict-aliasing violation si les deux i et f point pour les membres de la même union. Sur membre de l'union de l'accès via le "." l'accès des membres de l'opérateur, la spécification dit (6.5.2.3):

Un postfix expression suivie par la . opérateur et un identifiant désigne un membre d'une structure ou d'une union de l'objet. La valeur, c'est que de la de membres nommés (95) et est une lvalue si la première expression est une lvalue.

La note de bas de page 95 visées ci-dessus, dit:

Si le membre utilisé pour lire le contenu d'une union de l'objet n'est pas la même en tant que membre de la dernière utilisée pour stocker une valeur dans l'objet, le partie appropriée de l'objet de la représentation de la valeur est réinterprété comme un objet de représentation dans le nouveau type décrit dans 6.2.6 (un processus parfois appelé ‘type beaucoup les jeux de mots"). Cela pourrait être un piège de la représentation.

C'est clairement destiné à permettre le type de beaucoup les jeux de mots par l'intermédiaire d'un syndicat, mais il convient de noter que (1) notes de bas de page sont non-normative, qui est, ils ne sont pas censés proscrire les comportements, mais ils devraient clarifier l'intention d'une partie du texte en accord avec le reste de la spécification, et (2) cette provision pour type beaucoup les jeux de mots par l'intermédiaire d'un syndicat est réputé par les éditeurs de compilateurs comme s'appliquant uniquement pour l'accès par les membres de l'union opérateur d'accès - depuis stricte ailleurs aliasing est assez vide de sens, depuis à peu près tout potentiellement aliasing accède pourrait également être potentiellement membres de la même union.

Votre exemple les magasins via un pointeur vers un non-existant ou au moins non-actif membre de l'union, et ce qui, soit s'engage à une stricte aliasing violation (puisqu'il accède au membre qui est active à l'aide d'une lvalue de inadaptés type) ou utilise une lvalue qui ne désigne pas un objet (puisque l'objet correspondant à la non-membre actif n'existe pas) - il pourrait être soutenu de toute façon, et que la norme n'est pas particulièrement clair, mais de toute interprétation signifie que votre exemple a un comportement indéterminé.

(J'ajoute que je ne vois pas comment la note de bas de page permettant de type beaucoup les jeux de mots par l'intermédiaire d'un syndicat décrit le comportement qui est contraire inhérente dans le cahier des charges - qui est, il semble casser l'ISO règle de ne pas prescrire des comportements; rien d'autre dans le cahier des charges semble faire toute provision pour le type de beaucoup les jeux de mots par l'intermédiaire d'un syndicat. En outre, il est quelque chose d'un tronçon de lire le texte normatif comme exigeant que cette forme de type beaucoup les jeux de mots exige que l'accès doit être faite immédiatement par le type d'union).

Il y a souvent confusion causée par une autre partie de la spécification, toutefois, également en 6.5.2.3:

Une garantie spéciale est faite dans le but de simplifier l'utilisation des syndicats: si une union contient plusieurs structures qui partagent un tronc commun initial la séquence (voir ci-dessous), et si l'objet contient actuellement un de ces structures, il est autorisé à inspecter la commune initiale une partie de l'un d'eux partout où une déclaration de l'achèvement de type de l'union est visible.

Bien que cela ne s'applique pas à votre exemple, car il n'y a pas de commune de la séquence initiale, j'ai vu des gens lisent cela comme une règle générale régissant le type de beaucoup les jeux de mots (au moins quand une séquence initiale est impliqué); ils croient qu'il implique qu'il doit être possible d'utiliser ce type beaucoup les jeux de mots à l'aide de deux pointeurs vers les différents membres de l'union chaque fois que l'union complète la déclaration est visible (car des mots à cet effet apparaître dans le paragraphe cité ci-dessus). Cependant, je tiens à souligner que le paragraphe ci-dessus ne s'applique que pour les membres de l'union de l'accès via l'opérateur".". Le problème avec les concilier cette compréhension est, dans ce cas, la déclaration de l'union doit de toute façon être visible, car sinon vous ne seriez pas en mesure de consulter les membres de l'union. Je pense que c'est cette erreur dans le libellé, combinée avec la même mauvaise formulation de l'Exemple 3 (La suivante n'est pas valide fragment (parce que le type d'union n'est pas visible ...), lors de l'union, la visibilité n'est pas vraiment le facteur décisif), qui fait que certaines personnes de l'interpréter, que la commune de la-première-séquence exception est destiné à s'appliquer à l'échelle mondiale, et pas seulement pour l'accès des membres via l'opérateur".", comme une exception à la stricte aliasing règle; et, étant parvenu à cette conclusion, un lecteur pourrait alors interpréter la note de bas de page concernant le type de beaucoup les jeux de mots à appliquer à l'échelle mondiale également, et certains le font: voir la discussion sur ce bug de GCC par exemple (à noter que le bug a été dans l'état SUSPENDU pour une longue période).

(D'ailleurs, je suis conscient de plusieurs compilateurs qui ne mettent pas en œuvre le "bien commun global initial de la séquence de" la règle. Je ne suis pas spécialement au courant de tout les compilateurs qui mettent en œuvre le "bien commun global initial de la séquence de la" règle pas tout en permettant aussi de type arbitraire beaucoup les jeux de mots, mais cela ne signifie pas que ces compilateurs n'existent pas. Le comité de la réponse au Rapport de Défaut 257 suggère qu'ils ont l'intention de la règle globale, cependant, personnellement, je pense que l'idée que la simple visibilité d'un type qui devrait changer la sémantique de code qui ne fait pas référence à ce type est profondément erronée, et je sais que les autres sont d'accord).

À ce stade, vous pourriez poser la question de comment la lecture d'un non-actif membre de l'union par les états-opérateur d'accès ne viole pas les strictes de l'aliasing, si faire de même via un pointeur de fait. C'est encore un domaine où le cahier des charges est un peu floue, la clé est peut-être dans le choix des lvalue est responsable de l'accès. Par exemple, si un syndicat d'objet u a un membre de l' a et je l'ai lu via l'expression u.a, alors on pourrait interpréter cela comme un accès de l'objet membre (a) ou simplement comme un accès de l'union de l'objet (u) que le membre de la valeur est ensuite extrait à partir de. Dans ce dernier cas, il n'y a pas d'aliasing violation, car il est spécifiquement autorisé à accéder à un objet (c'est à dire le membre actif de l'objet) par l'intermédiaire d'une lvalue de type d'agrégat contenant approprié membre (6.5¶7). En effet, la définition de l'opérateur d'accès au membre dans 6.5.2.3 ne l'appui de cette interprétation, si peu faiblement: la valeur correspond à celle de l'nommé membre - alors qu'il est potentiellement une lvalue, il n'est pas nécessaire pour accéder à l'objet visé par cette lvalue afin d'obtenir la valeur du membre, et ainsi de stricte aliasing violation est évité. Mais c'est encore une fois étirant un peu.

(Pour moi, il semble sous-spécifiée, généralement, quand un objet a "sa valeur stockée et accessible ... par une lvalue expression" conformément à 6.5¶7; on peut bien sûr prendre une décision raisonnable, pour nous-mêmes, mais nous devons être prudents pour permettre de beaucoup les jeux de mots par les syndicats comme indiqué ci-dessus, ou autrement être prêt à ignorer note de bas de page 95. Malgré la verbiage inutile, le cahier des charges qui manque parfois dans les détails nécessaires).

Arguments au sujet de l'union de la sémantique, invariablement, reportez-vous à la RD 236 à un certain point. En effet, votre code d'exemple est superficiellement très similaire au code dans ce Rapport de Défaut. Je tiens à préciser que:

  1. "Le comité estime que l'Exemple 2 viole l'aliasing règles de 6,5 paragraphe 7" - ce n'est pas en contradiction avec mon raisonnement ci-dessus;
  2. "Afin de ne pas enfreindre les règles, la fonction f dans l'exemple devrait être écrit" - ce qui appuie mon raisonnement ci-dessus; vous devez utiliser de l'union de l'objet (et de l'opérateur".") pour changer de type de membre, sinon, vous accédez à un inexistante membre (depuis l'union peut contenir qu'un seul membre à la fois);
  3. L'exemple de la RD 236 est pas sur le type-beaucoup les jeux de mots. C'est sur si c'est ok pour l'attribuer à un non-actif membre de l'union par l'intermédiaire d'un pointeur à ce membre. Le code en question est subtilement différent de ce qui est en question ici, puisqu'il ne tente pas d'accéder à l ' "original" membre de l'union de nouveau après avoir écrit pour le second membre. Ainsi, en dépit de la similitude structurelle dans le code d'exemple, le Rapport de Défaut est largement indépendantes de votre question.
  4. Le Comité de la Réponse de la RD 236 affirme que "les Deux programmes invoquer un comportement indéfini". Ce n'est cependant pas pris en charge par la discussion, ce qui montre que l'Exemple 2 invoque un comportement indéterminé. Je crois que la réponse est erronée.

12voto

Purag Points 7403

En vertu de la définition des membres de l'union dans le §6.5.2.3:

3 postfix expression suivie par l' . de l'opérateur et un identifiant désigne un membre d'une structure ou d'une union de l'objet. ...

4 postfix expression suivie par l' -> de l'opérateur et un identifiant désigne un membre d'une structure ou d'une union de l'objet. ...

Voir aussi §6.2.3 ¶1:

  • les membres des structures ou des syndicats; chaque structure ou union a un nom distinct de l'espace pour ses membres (analysées par le type de l'expression utilisée pour accéder à un membre par l' . ou -> opérateur);

Il est clair que la note de bas de page 95 se réfère à l'accès d'un membre de l'union avec l'union dans le champ d'application et l'utilisation de l' . ou -> de l'opérateur.

Depuis des affectations et des accès pour les octets composant l'union ne sont pas faites par des membres de l'union, mais par le biais de pointeurs, votre programme n'invoque pas l'aliasing règles de membres de l'union (y compris ceux précisé par la note de bas de page 95).

En outre, normal aliasing règles sont violées depuis le efficace le type de l'objet après l' *f = 1.0 est float, mais sa valeur stockée est accessible par une lvalue de type int (voir §6.5 ¶7).

Remarque: Toutes les références citer ce C11 projet de norme.

5voto

a3f Points 3023

La norme C11 (§6.5.2.3.9 EXEMPLE 3) a l'exemple suivant:

Ce qui suit n'est pas un fragment valide (car le type d'union n'est pas visible dans la fonction f):

  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);
 }
 

Mais je ne peux pas trouver plus de précisions à ce sujet.

5voto

Eric J Points 51

La stricte aliasing règle interdit l'accès à un même objet par deux pointeurs qui n'ont pas de types compatibles, sauf si on est un pointeur vers un type de caractère:

7 Un objet doit avoir sa valeur stockée et accessible uniquement par une lvalue expression qui est l'un des types suivants:88)

  • un type compatible avec l'efficacité du type de l'objet,
  • une version qualifiée d'un type compatible avec l'efficacité du type de l'objet,
  • un type qui est signé ou non signé de type correspondant à l'efficacité du type de l'objet,
  • un type qui est signé ou non signé de type correspondant à une version qualifiée de l'effectif type de l'objet,
  • une agrégation ou une union de type qui comprend l'un des types mentionnés ci-dessus, parmi ses membres (y compris, de manière récursive, un membre d'un subaggregate ou contenus de l'union), ou
  • un type de caractère.

Dans votre exemple, *f = 1.0; , c'est de modifier fi.i, mais les types ne sont pas compatibles.

Je pense que l'erreur est de penser qu'une union contient n , où n est le nombre de membres. Un syndicat ne contient qu'un seul objet actif à tout moment pendant l'exécution du programme par le §6.7.2.1 ¶16

La valeur d'au plus un des membres peuvent être stockées dans une union de l'objet à tout moment.

L'appui de cette interprétation que l'union n'a pas simultanément contenir l'ensemble de ses membres, les objets peuvent être trouvées dans l'article 6.5.2.3:

et si l'union de l'objet actuellement contient l'un de ces structures

Enfin, à peu près la même question a été soulevée dans le rapport de défaut 236 en 2006.

Exemple 2

// optimization opportunities if "qi" does not alias "qd"
void f(int *qi, double *qd) {
    int i = *qi + 2;
    *qd = 3.1;       // hoist this assignment to top of function???
    *qd *= i;
    return;
}  

main() {
    union tag {
        int mi;
        double md;
    } u;
    u.mi = 7;
    f(&u.mi, &u.md);
}

Le comité estime que l'Exemple 2 viole l'aliasing règles en 6.5 paragraphe 7:

"un agrégat ou une union de type qui comprend une desdites types parmi ses membres (y compris, de manière récursive, un membre d'un subaggregate ou contenus de l'union)."

Afin de ne pas enfreindre les règles, la fonction f de l'exemple doit être écrite comme suit:

union tag {
    int mi;
    double md;
} u;

void f(int *qi, double *qd) {
    int i = *qi + 2;
    u.md = 3.1;   // union type must be used when changing effective type
    *qd *= i;
    return;
}

4voto

Peter Points 4026

Essentiellement la stricte aliasing règle décrit les circonstances dans lesquelles un compilateur est permis de supposer (ou, à l'inverse, pas permis de supposer) que les deux pointeurs de types différents ne pointent pas vers le même emplacement dans la mémoire.

Sur cette base, l'optimisation de vous décrire en strict_aliasing_example() est autorisé, car le compilateur n'est autorisé à assumer f et i point à des adresses différentes.

L' breaking_example() entraîne les deux pointeurs passés à l' strict_aliasing_example() de point à la même adresse. Cela rompt avec l'hypothèse que l' strict_aliasing_example() est autorisé à prendre, par conséquent, les résultats dans cette fonction présentant un comportement indéterminé.

Ainsi, le compilateur le comportement que vous décrivez est valide. C'est le fait qu' breaking_example() des causes les pointeurs passés à l' strict_aliasing_example() de point à la même adresse, ce qui provoque un comportement indéterminé - en d'autres termes, breaking_example() sauts de l'hypothèse que le compilateur est autorisé à faire dans strict_aliasing_example().

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X