Considérez les trois struct
s suivants:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
Sur les plates-formes typiques où int
est de 4 octets, les tailles, les alignements et le remplissage total 1 sont les suivants:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
Il n'y a pas de chevauchement entre le stockage des éléments blub
et blob
, même si la taille 1 blob
pourrait en principe "rentrer" dans le rembourrage de blub
.
C ++ 20 introduit l' no_unique_address
attribut, qui permet aux membres vides adjacents de partager la même adresse. Il permet également explicitement le scénario décrit ci-dessus d'utiliser le remplissage d'un membre pour en stocker un autre. De cppreference (soulignement le mien):
Indique que ce membre de données n'a pas besoin d'avoir une adresse distincte de tous les autres membres de données non statiques de sa classe. Cela signifie que si le membre a un type vide (par exemple Allocator sans état), le compilateur peut l'optimiser pour n'occuper aucun espace, comme s'il s'agissait d'une base vide. Si le membre n'est pas vide, tout remplissage de queue peut également être réutilisé pour stocker d'autres membres de données.
En effet, si nous utilisons cet attribut sur blub b0
la taille des bla
gouttes 8
, le blob
est en effet stocké dans le blub
comme vu sur godbolt .
Enfin, nous arrivons à ma question:
Quel texte dans les normes (C ++ 11 à C ++ 20) empêche ce chevauchement sans no_unique_address
, pour les objets qui ne sont pas trivialement copiables?
J'ai besoin d'exclure les objets trivialement copiables (TC) de ce qui précède, car pour les objets TC, il est autorisé de passer std::memcpy
d'un objet à un autre, y compris les sous-objets membres, et si le stockage était chevauché, cela se briserait (car tout ou partie du stockage pour le membre adjacent serait écrasé) 2 .
1 Nous calculons le remplissage simplement comme la différence entre la taille de la structure et la taille de tous ses membres constitutifs, récursivement.
2 Voilà pourquoi j'ai constructeurs de copie définis: pour faire blub
et blob
non trivialement copiable .
la source
Réponses:
Le standard est terriblement silencieux quand on parle du modèle de mémoire et pas très explicite sur certains des termes qu'il utilise. Mais je pense avoir trouvé une argumentation de travail (qui peut être un peu faible)
Voyons d'abord ce qui fait même partie d'un objet. [types.base] / 4 :
Ainsi, la représentation des objets se
b0
compose d'sizeof(blub)
unsigned char
objets, soit 8 octets. Les bits de remplissage font partie de l'objet.Aucun objet ne peut occuper l'espace d'un autre s'il n'est pas imbriqué en lui [basic.life] /1.5 :
Ainsi, la durée de vie de
b0
se terminerait, lorsque le stockage occupé par celui-ci serait réutilisé par un autre objet, c'est-à-direb1
. Je n'ai pas vérifié cela, mais je pense que la norme exige que le sous-objet d'un objet vivant soit également vivant (et je ne pouvais pas imaginer comment cela devrait fonctionner différemment).Ainsi, le stockage qui
b0
occupe ne peut pas être utilisé parb1
. Je n'ai trouvé aucune définition de «occuper» dans la norme, mais je pense qu'une interprétation raisonnable ferait «partie de la représentation de l'objet». Dans la représentation d'objet décrivant le devis, les mots "reprendre" sont utilisés 1 . Ici, ce serait 8 octets, donc il enbla
faut au moins un de plus pourb1
.Surtout pour les sous-objets (donc entre autres membres de données non statiques) il y a aussi la stipulation [intro.object] / 9 (mais cela a été ajouté avec C ++ 20, thx @BeeOnRope)
(soulignement le mien) Ici encore, nous avons le problème que "occupe" n'est pas défini et je dirais encore une fois de prendre les octets dans la représentation de l'objet. Notez qu'il y a une note de bas de page [basic.memobj] / note de bas de page 29
Ce qui peut permettre au compilateur de casser cela s'il peut prouver qu'il n'y a pas d'effet secondaire observable. Je pense que c'est assez compliqué pour une chose aussi fondamentale que la disposition des objets. C'est peut-être la raison pour laquelle cette optimisation n'est prise que lorsque l'utilisateur fournit les informations qu'il n'y a aucune raison d'avoir des objets disjoints en ajoutant l'
[no_unique_address]
attribut.tl; dr: le remplissage peut faire partie de l'objet et les membres doivent être disjoints.
1 Je n'ai pas pu résister à l'ajout d'une référence qui pourrait signifier occuper: Webster's Revised Unabridged Dictionary, G. & C. Merriam, 1913 (c'est moi qui souligne)
Quelle analyse standard serait complète sans analyse de dictionnaire?
la source
no_unique_address
. Cela laisse la situation avant C ++ 20 moins claire. Je n'ai pas compris votre raisonnement conduisant à "Aucun objet ne peut occuper l'espace d'un autre s'il n'est pas imbriqué" à partir de basic.life/1.5, en particulier comment obtenir de "le stockage que l'objet occupe est libéré" à "aucun objet ne peut occuper l'espace d'un autre".