56 votes

reinterpret_cast création d'un objet trivialement constructible par défaut

Référence cpp † déclare que :

Les objets avec des constructeurs par défaut triviaux peuvent être créés en utilisant la fonction reinterpret_cast sur n'importe quelle mémoire alignée de manière appropriée, par exemple sur une mémoire allouée avec l'option std::malloc .

Cela implique que le code suivant est bien défini :

struct X { int x; };
alignas(X) char buffer[sizeof(X)];    // (A)
reinterpret_cast<X*>(buffer)->x = 42; // (B)

Trois questions suivent :

  1. Cette citation est-elle correcte ?
  2. Si oui, à quel moment la durée de vie de la X commencer ? Si en ligne (B) Est-ce que c'est le plâtre lui-même qui est considéré comme un stockage d'acquisition ? Si c'est en ligne (A) et s'il y avait une branche entre (A) y (B) qui construirait conditionnellement un X ou une autre capsule, Y ?
  3. Y a-t-il des changements entre C++11 et C++1z à cet égard ?

† Notez qu'il s'agit d'un ancien lien. Le libellé a été modifié en réponse à cette question. Il se lit maintenant comme suit :

Contrairement au C, cependant, les objets avec des constructeurs par défaut triviaux ne peuvent pas être créés en réinterprétant simplement un stockage correctement aligné, tel que la mémoire allouée avec std::malloc : placement-new est nécessaire pour introduire formellement un nouvel objet et éviter un comportement potentiel non défini.

0 votes

J'ai essayé de répondre à la question de savoir quand commence la vie de ces objets. Je n'ai pas été en mesure de trouver une réponse définitive dans la norme, et je crois qu'elle est vague à cet égard. Pour ce qui est de la première question, je doute que la citation soit correcte, car il faut faire attention à la règle d'aliasing.

0 votes

@SergeyA tant que le tampon est un tampon char, l'aliasing strict n'est pas un problème.

3 votes

Non, et je croyais qu'on avait déjà parlé de ça plusieurs fois ? [intro.object]/1 énumère de manière exhaustive les constructions du langage qui peuvent créer des objets.

37voto

T.C. Points 22510

Il n'y a pas X objet, vivant ou non, donc prétendre qu'il en existe un entraîne un comportement non défini.

[intro.objet]/1 énonce de manière exhaustive le moment où les objets sont créés :

Un site objet est créé par une définition ([basic.def]), par un fichier nouvelle-expression ([expr.new]), lorsque l'on change implicitement le membre actif membre actif d'une union ([class.union]), ou quand un objet temporaire est temporaire ([conv.rval], [class.temporary]).

Avec l'adoption de P0137R1 La définition du terme "objet" se trouve dans ce paragraphe.

Existe-t-il une définition d'un X objet ? Non. Y a-t-il un nouvelle-expression ? Non. Y a-t-il une union ? Non. Y a-t-il une construction de langage dans votre code qui crée un objet temporaire X objet ? Non.

Ce que [basic.life] dit sur la durée de vie d'un objet avec une initialisation vide n'est pas pertinent. Pour que cela s'applique, vous devez avoir un objet en premier lieu. Vous n'en avez pas.

C++11 a à peu près le même paragraphe, mais ne l'utilise pas comme définition d'"objet". Néanmoins, l'interprétation est la même. L'autre interprétation - traiter [basic.life] comme créant un objet dès qu'un stockage approprié est obtenu - signifie que vous créez des objets de Schrödinger. * ce qui contredit N3337 [intro.objet]/6 :

Deux objets qui ne sont pas des champs de bits peuvent avoir la même adresse si un est un sous-objet de l'autre, ou si au moins l'un d'entre eux est un sous-objet de classe de base de taille nulle et que l'autre est un objet de classe de base. sous-objet de taille nulle et qu'ils sont de types différents ; sinon ils doivent avoir des adresses distinctes.


<sup>* </sup>Stockage avec l'alignement et la taille appropriés pour un type <code>T</code> est par définition un stockage avec l'alignement et la taille appropriés pour <em>tout autre type </em>dont les exigences de taille et d'alignement sont égales ou inférieures à celles des <code>T</code> . Ainsi, cette interprétation signifie que l'obtention du stockage crée simultanément un ensemble infini d'objets de types différents dans ledit stockage, ayant tous la même adresse.

0 votes

Donc rien dans [basic.lval]/8 n'est pertinent parce qu'il n'y a pas de "type de l'objet" par lequel nous accédons parce qu'il n'y a pas d'objet ?

0 votes

Eh bien, il y a un objet - buffer le char le tableau.

2 votes

Avec le plus grand respect (ce n'est pas de la langue de bois, vos réponses sont très instructives), si c'était le cas, alors l'allocation d'un objet via malloc serait un comportement non défini. Pourtant, le §3.8 l'autorise explicitement. Il semble y avoir un décalage dans la formulation de la norme.

4voto

Amir Kirsh Points 1264

Sur la base de p0593r6 Je pense que le code figurant dans le PO est valable et doit être bien défini. Le nouveau libellé, basé sur le DR appliqué rétroactivement à toutes les versions à partir de C++98 inclus, permet de création implicite d'objets pour autant que l'objet créé soit bien défini ( tautologie est parfois le secours pour les définitions compliquées), voir § 6.7.2.11 Modèle d'objet [intro.objet] ) :

objets créés implicitement dont l'adresse est celle du début de la région de de la région de stockage, et produire une valeur de pointeur qui pointe sur cet objet, si cette valeur permet au programme d'avoir un comportement défini. comportement [...]

Voir aussi : https://stackoverflow.com/a/61999151/2085626

3voto

Jerry Coffin Points 237758

Cette analyse est basée sur n4567, et utilise les numéros de section de celui-ci.

§5.2.10/7 : Quand une valeur prvalue v de type pointeur d'objet est converti en pointeur d'objet de type "pointeur vers cv T", le résultat est static_cast<cv T*>(static_cast<cv void*>(v)) .

Donc, dans ce cas, le reinterpret_cast<X*>(buffer) est la même chose que static_cast<X *>(static_cast<void *>(buffer)) . Cela nous amène à examiner les parties pertinentes concernant static_cast :

§5.2.9/13 : Une valeur prval de type "pointeur vers cv1 void" peut être converti en une valeur prval de type "pointeur vers cv2 T", où T est un type d'objet et cv2 est identique à, ou supérieur à, cv-qualification, cv1 . La valeur du pointeur nul est convertie en la valeur du pointeur nul du type de destination. Si la valeur du pointeur d'origine représente l'adresse A d'un octet en mémoire et A satisfait à l'exigence d'alignement de T alors la valeur du pointeur résultant représente la même adresse que la valeur du pointeur d'origine, c'est-à-dire, A .

Je crois que c'est suffisant pour dire que la citation originale est en quelque sorte correcte - cette conversion donne des résultats définis.

Quant à la durée de vie, elle dépend de ce dont vous parlez. Le cast crée un nouvel objet de type pointeur - un temporaire, dont la durée de vie commence à la ligne où se trouve le cast et se termine lorsqu'il sort de la portée. Si vous avez deux conversions différentes qui se produisent de manière conditionnelle, chaque pointeur a une durée de vie qui commence à l'emplacement du cast qui l'a créé.

Ni l'un ni l'autre n'affecte la durée de vie de l'objet fournissant le stockage sous-jacent, qui est toujours buffer et a exactement la même durée de vie, que vous créiez ou non un pointeur (de type identique ou converti) vers ce stockage.

0 votes

Mais quelle est la conclusion ? Est-ce que votre affirmation est que le pointeur de X est créée légalement, mais qu'elle ne peut pas être déréférencée (par exemple, la fonction ->x est UB) parce qu'ils ne pointent pas vers une création X objet ? La pertinence de la durée de vie des pointeurs eux-mêmes n'est pas claire et il est difficile de comprendre de quel côté du débat cette réponse se situe.

1 votes

Oui, la création du pointeur a un comportement défini, mais le déréférencement du pointeur donne UB. J'ai considéré que sa question sur la durée de vie était quelque peu ambiguë, j'ai donc indiqué la durée de vie de chaque objet dans le code, même si je suis d'accord sur le fait que la durée de vie des pointeurs eux-mêmes probablement n'est pas ce dont il se souciait. Il a demandé quelle était la durée de vie de la X et il n'y a pas de véritable X impliqué, juste un pointeur sur X initialisé avec l'adresse d'un buffer de char s.

0 votes

Oui, mais à la fin, le code déréférence le pointeur comme s'il y avait fue un X - si cela ne fonctionne pas (ce qui est le cœur de la question, en fait), vous pourriez peut-être le signaler ?

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