Est-ce que memcpy d'un type de construction ou d'affectation trivial-copiable?
disons que vous avez un objet de type T
et un tampon mémoire convenablement aligné alignas(T) unsigned char[sizeof(T)]
. Si vous utilisez std::memcpy
copier à partir de l'objet de type T
unsigned char
array, est considéré que la copie de la construction ou de la copie d'affectation?
Si un type est trivialement-copiable mais pas standard-layout, il est concevable qu'une classe comme ceci:
struct Meow
{
int x;
protected: // different access-specifier means not standard-layout
int y;
};
peut être implémenté comme ceci, parce que le compilateur n'est pas forcé d'utiliser standard-layout:
struct Meow_internal
{
private:
ptrdiff_t x_offset;
ptrdiff_t y_offset;
unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
Le compilateur pourrait magasin x
et y
de Miaou dans la zone tampon à n'importe quelle partie de buffer
, peut-être même à un décalage aléatoire à l'intérieur de buffer
tant qu'ils sont correctement alignés et ne se chevauchent pas. Le décalage de x
et y
peut même varier de façon aléatoire avec chaque construction si le compilateur le souhaite. (x
aller après y
si le compilateur le souhaite parce que la norme n'exige que des membres du même spécificateur d'accès pour aller dans l'ordre, et x
et y
ont des spécificateurs d'accès différents.)
cela répondrait aux exigences d'être trivialement-copiable; a memcpy
copierait les champs offset cachés, donc la nouvelle copie fonctionnerait. Mais certaines choses ne fonctionnent pas. Par exemple, la tenue d'un pointeur x
à travers un memcpy
pause:
Meow a;
a.x = 2;
a.y = 4;
int *px = &a.x;
Meow b;
b.x = 3;
b.y = 9;
std::memcpy(&a, &b, sizeof(a));
++*px; // kaboom
cependant, le compilateur est-il vraiment autorisé à implémenter une classe trivialement-copiable de cette manière? Un déréférencement px
devrait seulement être un comportement indéfini si <!-La vie de 22 ans est terminée. Il a? Les parties pertinentes du projet de norme N3797 ne sont pas très claires à ce sujet. C'est la section [base.la vie]/1:
durée de vie d'un objet est une propriété de l'objet. Un on dit que l'objet a une initialisation non triviale s'il s'agit d'une classe ou agrégat et elle ou l'un de ses membres est initialisée par un constructeur d'autre qu'un banal constructeur par défaut. [ Remarque: l'initialisation par un constructeur de copie/déplacement trivial n'est pas trivial initialisation. - note ] La durée de vie d'un objet de type
T
commence quand:
- stockage avec le bon alignement et la taille pour le type
T
et- si l'objet a une initialisation non triviale, son initialisation est complète.
La durée de vie d'un objet de type
T
se termine lorsque:
- si
T
est un type de classe avec un destructeur non trivial ([la classe.dtor]), l'appel du destructeur démarre, ou- le stockage que l'objet occupe est réutilisé ou libéré.
Et c'est [base.types] / 3:
pour tout objet (autre qu'un sous-objet de classe de base) de forme triviale type copiable
T
, que l'objet possède ou non une valeur valide de typeT
, les octets sous-jacents ([intro.mémoire]) l'objet peut être copié dans un tableau dechar
ouunsigned char
. Si le contenu du tableau dechar
ouunsigned char
copié dos dans l'objet, l'objet doit ensuite tenir son origine valeur. exemple omis
La question devient alors: est un memcpy
écrasement d'une instance de classe trivialement copiable "copy construction" ou "copie-cession"? La réponse à la question semble décider si Meow_internal
est un moyen valide pour un compilateur d'implémenter une classe trivialement-copiable Meow
.
Si memcpy
est "copie de la construction", alors la réponse est que Meow_internal
est valide, car la construction de la copie réutilise la mémoire. Si memcpy
"copier-cession", alors la réponse est que Meow_internal
n'est pas une implémentation valide, car l'assignation n'invalide pas les pointeurs vers les membres instanciés d'une classe. Si memcpy
est à la fois, je n'ai aucune idée de ce qu'est la réponse.
2 réponses
pour moi Il est clair que l'utilisation de std::memcpy
n'entraîne ni construction ni assignation. Il n'est pas de la construction, car aucun constructeur sera appelé. Il n'est ni affectation, que l'opérateur d'affectation ne sera pas appelé. Étant donné qu'un objet trivialement copiable possède des destructeurs triviaux, des constructeurs (copy/move) et des opérateurs d'affectation (copy/move), le point est plutôt discutable.
vous semblez avoir cité le numéro 2 de l'article 3.9 [basic.type.] Au numéro 3, il est écrit:
Pour toute trivialement copiable type
T
, si deux pointeursT
point de distinctT
objetsobj1
etobj2
, où ni l'un ni l'autreobj1
niobj2
est un sous-objet de classe de base, si les octets sous-jacents (1.7) constituentobj1
sont copiés dansobj2
, 41obj2
doit ensuite contenir la même valeur qu'obj1
. [ Exemple:
T* t1p;
T* t2p;
// à condition quet2p
renvoie à un objet initialisé ...
std::memcpy(t1p, t2p, sizeof(T));
// à ce point, chaque sous-objet de type trivialement copiable dans*t1p
contient
// la même valeur que le sous-objet dans*t2p
la fin de l'exemple ]
41) En utilisant, par exemple, les fonctions de la bibliothèque (17.6.1.2)std::memcpy
oustd::memmove
.
de toute évidence, la norme visait à permettre *t1p
pour être utilisable dans tous les sens *t2p
serait.
suite au numéro 4:
représentation de l'objet d'un objet de type
T
est la séquence de N non signé char objets repris par l'objet de typeT
, où N égalesizeof(T)
. valeur représentation d'un objet est l'ensemble de bits qui maintiennent la valeur de typeT
. Pour les types trivialement copiables, la représentation de valeur est un ensemble de bits dans la représentation d'objet qui détermine une valeur, qui est un élément discret d'un ensemble de valeurs défini par implémentation. 42
42) l'intention est que le modèle de mémoire de C++ soit compatible avec celui du langage de programmation ISO/IEC 9899 C.
L'utilisation le mot devant les deux termes définis implique que tout type donné a seulement la représentation d'un objet et un objet donné n'a que représentation de la valeur. Votre morphing interne hypothétique ne devrait pas exister. La note de bas de page indique clairement que l'intention est pour les types trivialement copiables d'avoir une mise en page mémoire compatible avec C. L'attente est alors que même un objet avec une mise en page non standard, le copier permettra tout de même de l'utiliser.
Dans le même projet, vous trouverez également le texte suivant, directement à la suite du texte que vous avez cité:
Pour toute trivialement copiable type
T
, si deux pointeursT
point de distinctT
objetsobj1
etobj2
, où niobj1
niobj2
est un sous-objet de classe de base, si les octets sous-jacents (1.7) constituentobj1
sont copiés dansobj2
,obj2
doit ensuite contenir la même valeur qu'obj1
.
Note que cela parle d'un changement de la valeur de obj2
, et non pas de détruire l'objet obj2
et de créer un nouvel objet à sa place. Puisque ce n'est pas l'objet, mais seulement sa valeur qui est changée, tout pointeur ou référence à ses membres devrait donc rester valable.