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 Tunsigned 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 type T, les octets sous-jacents ([intro.mémoire]) l'objet peut être copié dans un tableau de char ou unsigned char. Si le contenu du tableau de char ou unsigned 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.

26
demandé sur T.C. 2014-10-03 04:53:11

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 pointeurs T point de distinct T objets obj1 et obj2, où ni l'un ni l'autre obj1 ni obj2 est un sous-objet de classe de base, si les octets sous-jacents (1.7) constituent obj1 sont copiés dans obj2, 41obj2 doit ensuite contenir la même valeur qu' obj1. [ Exemple:

  T* t1p;

  T* t2p;

          // à condition quet2prenvoie à un objet initialisé ...

  std::memcpy(t1p, t2p, sizeof(T));

          // à ce point, chaque sous-objet de type trivialement copiable dans*t1pcontient

          // 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 ou std::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 type T, où N égale sizeof(T). valeur représentation d'un objet est l'ensemble de bits qui maintiennent la valeur de type T. 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.

6
répondu jxh 2016-07-16 00:18:12

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 pointeurs T point de distinct T objets obj1 et obj2, où ni obj1 ni obj2 est un sous-objet de classe de base, si les octets sous-jacents (1.7) constituent obj1 sont copiés dans obj2,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.

2
répondu celtschk 2015-05-08 00:45:58