58 votes

Violer le crénelage strict en C, même sans casting?

Comment peut - *i et u.i imprimer des numéros différents dans le présent code, même s' i est défini comme int *i = &u.i;? Je ne peux que supposer que je suis déclenchement UB ici, mais je ne vois pas comment exactement.

(ideone démo réplique si je sélectionne " C " comme la langue. Mais comme @2501 l'a souligné, pas si "C99 stricte" est la langue. Mais là encore, j'ai eu le problème avec gcc-5.3.0 -std=c99!)

// gcc       -fstrict-aliasing -std=c99   -O2
union
{   
    int i;
    short s;
} u;

int     * i = &u.i;
short   * s = &u.s;

int main()
{   
    *i  = 2;
    *s  = 100;

    printf(" *i = %d\n",  *i); // prints 2
    printf("u.i = %d\n", u.i); // prints 100

    return 0;
}

(gcc 5.3.0, -fstrict-aliasing -std=c99 -O2, également avec -std=c11)

Ma théorie est qu' 100 est la "bonne" réponse, parce que l'écriture pour les membres de l'union à travers l' short-lvalue *s est définie en tant que telle (pour cette plate-forme/stockage/whatever). Mais je pense que l'optimiseur ne se rend pas compte que l'écriture d' *s peut-alias u.i, et, par conséquent, elle estime que ceux - *i=2; est la seule ligne qui peut affecter *i. Est-ce raisonnable de la théorie?

Si *s peut-alias u.i, et u.i peut-alias *i, alors sûrement que le compilateur doit penser qu' *s peut-alias *i? Ne devrait pas aliasing être "transitif'?

Enfin, j'ai toujours eu cette hypothèse que le strict-aliasing problèmes ont été causés par le mauvais casting. Mais il n'y a pas de casting dans ce!

(Mon fond est le C++, j'espère que je me suis poser une question raisonnable sur le C ici. Mon (limitée) de la compréhension, c'est que, en C99, il est acceptable d'écrire grâce à un membre de l'union, puis la lecture par un autre membre d'un type différent.)

56voto

Grzegorz Szpetkowski Points 10225

Le disrepancy est délivré par -fstrict-aliasing option d'optimisation. Son comportement et de pièges possibles sont décrites dans la documentation de GCC:

Portez une attention particulière à un code comme ceci:

      union a_union {
        int i;
        double d;
      };

      int f() {
        union a_union t;
        t.d = 3.0;
        return t.i;
      }

La pratique de la lecture à partir d'un autre membre de l'union de l'un plus récemment écrit (appelé "type de beaucoup les jeux de mots") est commun. Même avec -fstrict-aliasing, type beaucoup les jeux de mots est autorisé, à condition que la mémoire est accessible par le type d'union. Donc, le code ci-dessus fonctionne comme prévu. Voir les Structures des syndicats des énumérations et des bits de champs de mise en œuvre. Toutefois, ce code pourrait pas:

      int f() {
        union a_union t;
        int* ip;
        t.d = 3.0;
        ip = &t.i;
        return *ip;
      }

Notez que de la conformation de la mise en œuvre est parfaitement autorisé à prendre avantage de cette optimisation, comme deuxième exemple de code présente un comportement indéterminé. Voir l'Olaf et les réponses des autres pour référence.

18voto

Olaf Points 9969

C standard (c'est à dire C11, n1570), 6.5p7:

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

  • ...
  • 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.

La lvalue expressions de votre pointeurs sont pas union types, donc cette exception ne s'applique pas. Le compilateur est correct exploiter ce comportement indéfini.

Faire les pointeurs de types pointeurs à l' union type et de déréférencement avec le membre concerné. Qui devrait fonctionner:

union {
    ...
} u, *i, *p;

12voto

Matt McNabb Points 14273

Stricte de l'aliasing est underspecified dans le C Standard, mais l'interprétation habituelle que l'union de l'aliasing (qui remplace le strict aliasing) n'est autorisé que si les membres du syndicat sont directement accessibles par leur nom.

Pour la justification de cette considérer:

void f(int *a, short *b) { 

Le but de la règle est que le compilateur peut supposer a et b ne sont pas d'alias, et de générer un code efficace, en f. Mais si le compilateur a dû intégrer le fait qu' a et b pourrait être le chevauchement des membres de l'union, en effet, il ne pouvait pas faire de ces hypothèses.

Si les deux pointeurs sont les paramètres de la fonction ou non n'a aucune importance, la stricte aliasing règle ne fait pas la différence sur cette base.

7voto

Serge Ballesta Points 12850

Ce code, en effet, appelle UB, parce que vous n'avez pas le respect de la stricte aliasing règle. n1256 projet de C99 états 6.5 Expressions §7:

Un objet doit avoir sa valeur stockée et accessible uniquement par une lvalue expression qui est l'un des les types suivants:
- 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'effectif type de la objet,
- un type qui est signé ou non signé de type correspondant à une version qualifiée de l' efficace type de l'objet,
- une agrégation ou une union de type qui comprend l'un des types mentionnés ci-dessus parmi ses les membres (y compris, de manière récursive, un membre d'un subaggregate ou contenus de l'union), ou
- un type de caractère.

Entre l' *i = 2; et de la printf(" *i = %d\n", *i); seulement une courte distance de l'objet est modifiée. Avec l'aide de la stricte aliasing règle, le compilateur est libre de supposer que l'objet int pointée par i n'a pas été modifié, et il peut utiliser directement une valeur mise en cache sans le recharger à partir de la mémoire principale.

Il est manifestement pas ce qu'est un être humain normal pourrait s'y attendre, mais la stricte aliasing règle a été précisément conçue pour permettre l'optimisation des compilateurs pour utiliser les valeurs en cache.

Pour la deuxième impression, les syndicats sont référencés dans la même norme dans 6.2.6.1 Représentations de types / Général §7:

Lorsqu'une valeur est stockée dans un membre d'un objet de type union, les octets de l'objet la représentation qui ne correspond pas à ce membre, mais ne correspondent à d'autres membres prendre de quelconques valeurs.

Ainsi que u.s a été enregistrée, u.i ont pris une valeur non spécifiée par la norme

Mais on peut lire plus loin dans 6.5.2.3 de la Structure et des membres de l'union §3, note 82:

Si le membre utilisé pour accéder au contenu d'une union de l'objet n'est pas le même que le membre de la dernière de stocker une valeur dans l'objet, la partie appropriée de l'objet de la représentation de la valeur, est réinterprétée 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.

Bien que les notes ne sont pas normatifs, ils permettent une meilleure compréhension de la norme. Lors de l' u.s ont été enregistrées à l' *s pointeur, les octets correspondant à un court ont été modifiés pour la 2 valeur. En supposant un petit-boutiste du système, comme 100 est plus petite que la valeur de l'une court, la représentation est un int devrait maintenant être de 2 comme élevée afin d'octets ont été 0.

TL/DR: même si c'est pas normatif, la note 82 devraient exiger que sur une little endian système x86 ou x64 familles, printf("u.i = %d\n", u.i); imprime 2. Mais par la stricte aliasing règle, le compilateur a le droit de supposer que la valeur pointée par i n'a pas changé et peut imprimer 100

6voto

John Bollinger Points 16563

Vous sonder un peu controversé du C standard.

C'est la stricte aliasing règle:

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

  • 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'effectif 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),
  • un type de caractère.

(C2011, 6.5/7)

La lvalue expression *i type int. La lvalue expression *s type short. Ces types ne sont pas compatibles les uns avec les autres, ni à la fois compatible avec tout autre type particulier, pas plus que le strict aliasing règle permettre de toute autre alternative qui permet à la fois un accès conforme si les pointeurs sont des alias.

Si au moins l'un des accès est non-conforme, alors le comportement est indéfini, de sorte que le résultat vous rapport-ou d'ailleurs toute autre raison, est tout à fait acceptable. Dans la pratique, le compilateur doit produire du code qui réorganise les affectations de l' printf() des appels, ou qui utilise un précédemment chargé de la valeur de *i à partir d'un registre, au lieu de re-lecture de la mémoire, ou quelque chose de similaire.

Ladite controverse se pose parce que les gens vont parfois point à la note de bas de page 95:

Si le membre utilisé pour lire le contenu d'une union de l'objet n'est pas le même que le dernier membre utilisé pour stocker une valeur dans l'objet, la 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.

Notes de bas de page sont données à titre indicatif, cependant, pas normatif, donc il n'y a pas vraiment de question à laquelle le texte gagne si elles entrent en conflit. Personnellement, je trouve la note de bas de page tout simplement comme un guide de mise en œuvre, de préciser la signification du fait que le stockage pour les membres du syndicat des chevauchements.

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