cppreference †表示:
可以使用
reinterpret_cast
在任何适当对齐的存储上创建具有普通默认构造函数的对象,例如在使用std::malloc
分配的内存上。
这意味着以下是明确定义的代码:
struct X { int x; };
alignas(X) char buffer[sizeof(X)]; // (A)
reinterpret_cast<X*>(buffer)->x = 42; // (B)
以下三个问题:
X
的生命周期在什么时候开始?如果在线(B)
,那么它是否被视为获取存储?如果在(A)
行,如果(A)
和(B)
之间存在有条件构建X
或其他某个广告单元Y
的分支怎么办?†请注意,这是一个旧链接。针对这个问题,措辞发生了变化。它现在写着:
与C不同,不能通过简单地重新解释适当对齐的存储来创建具有普通默认构造函数的对象,例如使用
std::malloc
分配的内存:placement-new是正式引入新对象并避免潜在的未定义所必需的行为。
答案 0 :(得分:32)
没有X
个对象,无论是生活还是其他,所以假装有一个会导致未定义的行为。
[intro.object]/1在创建对象时详尽阐述:
对象由定义([basic.def])创建,由a创建 隐式更改活动时, new-expression ([expr.new]) union的成员([class.union]),或临时对象的时候 created([conv.rval],[class.temporary])。
采用P0137R1后,本段是“对象”一词的定义。
是否有X
对象的定义?没有。 new-expression ?没有。有工会吗?没有。您的代码中是否有一个语言结构可以创建一个临时的X
对象?否。
无论[basic.life]说什么关于具有空洞初始化的对象的生命周期都是无关紧要的。要申请,您必须首先拥有一个对象。你没有。
C ++ 11具有大致相同的段落,但不将其用作“对象”的定义。尽管如此,解释是一样的。另一种解释 - 在获得合适的存储空间后立即将[basic.life]视为创建对象 - 意味着您正在创建Schrödinger的对象 * ,这与N3337 [intro.object]/6相矛盾:
如果不是位字段,则两个对象可能具有相同的地址 是另一个的子对象,或者如果至少有一个是基类 零大小的子对象,它们是不同类型的;除此以外, 他们应该有不同的地址。
* 对于类型T
,具有正确对齐和大小的存储是按照定义存储的,具有所有其他类型的正确对齐和大小其大小和对齐要求等于或小于T
的大小和对齐要求。因此,该解释意味着同时获得存储在所述存储器中创建具有不同类型的无限对象集,所有对象都具有相同的地址。 子>
答案 1 :(得分:3)
此分析基于n4567,并使用其中的章节编号。
§5.2.10/ 7:当对象指针类型的prvalue v
转换为对象指针类型“指向 cv T的指针”时,结果为{{1} }。
因此,在这种情况下,static_cast<cv T*>(static_cast<cv void*>(v))
与reinterpret_cast<X*>(buffer)
相同。这导致我们查看有关static_cast<X *>(static_cast<void *>(buffer))
的相关部分:
§5.2.9/ 13:类型“指向 cv1 void的指针”的prvalue可以转换为“指向 cv2 T的指针”的prvalue,其中T是对象类型, cv2 与 cv1 具有相同的cv资格,或更高的cv资格。空指针值将转换为目标类型的空指针值。如果原始指针值表示内存中字节的地址static_cast
,并且A
满足A
的对齐要求,则结果指针值表示与原始指针值相同的地址,也就是T
。
我相信这足以说原始报价是正确的 - 这种转换会给出明确的结果。
关于生命,它取决于你所说的一生。强制转换创建了一个指针类型的新对象 - 一个临时对象,它具有从演员阵容所在的行开始的生命周期,并在它超出范围时结束。如果有两个不同的转换是有条件的,那么每个指针的生命周期都从创建它的转换的位置开始。
这些都不会影响提供底层存储的对象的生命周期,它仍然是A
,并且具有完全相同的生命周期,无论您是创建指针(具有相同或转换类型),是否存储。
答案 2 :(得分:3)
基于p0593r6,我认为OP中的代码是有效的,应该进行正确定义。新的措辞基于可追溯地适用于C ++ 98(包括C ++ 98)所有版本的DR,只要创建的对象定义正确(重言式有时是对复杂定义的帮助),请参见第6.7.2.11节“对象模型[intro.object]):
隐式创建的对象,其地址为起始地址 的存储区域,并产生一个指向的指针值 该对象,如果该值将导致程序已定义 行为[...]