Considérez la structure suivante:
struct s {
int a, b;
};
Typiquement 1 , cette structure aura la taille 8 et l'alignement 4.
Et si nous créons deux struct s
objets (plus précisément, nous écrivons dans le stockage alloué deux de ces objets), le deuxième objet chevauchant le premier?
char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4
// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};
printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);
Y a-t-il quelque chose sur ce comportement indéfini du programme? Si oui, où devient-il indéfini? Si ce n'est pas UB, est-il garanti d'imprimer toujours ce qui suit:
o2.a=3
o2.b=4
o1.a=1
o1.b=3
En particulier, je veux savoir ce qui arrive à l'objet pointé par o1
quand o2
, qui le chevauche, est écrit. Est-il toujours autorisé à accéder à la partie non nettoyée ( o1->a
)? L'accès à la partie encombrée est-il o1->b
simplement identique à l'accès o2->a
?
Comment le type efficace s'applique-t-il ici? Les règles sont suffisamment claires lorsque vous parlez d'objets qui ne se chevauchent pas et de pointeurs pointant vers le même emplacement que le dernier magasin, mais lorsque vous commencez à parler du type effectif de parties d'objets ou d'objets qui se chevauchent, c'est moins clair.
Est-ce que quelque chose changerait si la deuxième écriture était d'un type différent? Si les membres se disaient int
et short
plutôt que deux int
s?
Voici un coup de foudre si vous voulez y jouer avec.
1 Cette réponse s'applique aux plates-formes où ce n'est pas le cas également: par exemple, certaines pourraient avoir la taille 4 et l'alignement 2. Sur une plate-forme où la taille et l'alignement étaient les mêmes, cette question ne s'appliquerait pas car des objets alignés et se chevauchant être impossible, mais je ne suis pas sûr qu'il existe une telle plate-forme.
Réponses:
Fondamentalement, c'est toute la zone grise dans la norme; la règle d'alias stricte spécifie les cas de base et laisse le lecteur (et les éditeurs de compilateurs) remplir les détails.
Il y a eu des efforts pour écrire une meilleure règle mais jusqu'à présent, ils n'ont abouti à aucun texte normatif et je ne sais pas quel est le statut de ceci pour C2x.
Comme mentionné dans ma réponse à votre question précédente, l'interprétation la plus courante est que les
p->q
moyens(*p).q
et le type efficace s'appliquent à tous*p
, même si nous continuons ensuite à appliquer.q
.Selon cette interprétation,
printf("o1.a=%d\n", o1->a);
cela entraînerait un comportement indéfini car le type effectif de l'emplacement*o1
ne l'est pass
(car une partie de l'emplacement a été écrasée).La justification de cette interprétation peut être vue dans une fonction comme:
Avec cette interprétation, la dernière ligne pourrait être optimisée
puts("5");
, mais sans elle, le compilateur devrait considérer que l'appel de fonction peut avoir étéf(o1, o2);
et donc perdre tous les avantages prétendument fournis par la règle d'aliasing stricte.Un argument similaire s'applique à deux types de structure non liés qui ont tous deux un
int
membre à un décalage différent.la source
f(s* s1, s* s2)
, sansrestrict
, le compilateur ne peut pas assumers1
ets2
sont des pointeurs différents. Je pense , encore une fois sansrestrict
, qu'il ne peut même pas supposer qu'ils ne se chevauchent pas partiellement. IAC, je ne vois pas que la préoccupation d'OP soit bien démontrée par l'f()
analogie. Bonne chance sans grondement. UV pour la première moitié.s1 == s2
serait autorisé, mais pas de chevauchement partiel. (L'optimisation dans mon exemple de code pourrait toujours être effectuée sis1 == s2
)int
au lieu de structs (et un système avec_Alignof(int) < sizeof(int)
).p->q
et(*p).q
. Cela peut être vrai pour le type d'interprétation comme vous le dites, mais ce n'est pas vrai d'un point de vue opérationnel. Il est important pour les accès simultanés à la même structure qu'un accès d'un membre n'implique pas l'accès d'un autre membre.E1.E2
expression n'effectue pas d'accès (je veux dire l'E1
expression entière . Certaines de ses sous-expressions peuvent effectuer l'accès. C'est-à-dire siE1
c'est(*p)
, puis lire la valeur du pointeur lors de l'évaluationp
est l'accès, mais l'évaluation de*p
ou(*p)
n'effectue aucune accès). La règle de repliement strict ne s'applique pas en cas d'absence d'accès.