reinterpret_cast 创建一个普通的默认构造对象
reinterpret_cast creating a trivially default-constructible object
cppreference† 指出:
Objects with trivial default constructors can be created by using reinterpret_cast
on any suitably aligned storage, e.g. on memory allocated with 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
或其他 pod,Y
怎么办?
- 在这方面,C++11 和 C++1z 之间有什么变化吗?
†注意这是旧的link。措辞已针对此问题进行了更改。现在是:
Unlike in C, however, objects with trivial default constructors cannot be created by simply reinterpreting suitably aligned storage, such as memory allocated with std::malloc
: placement-new is required to formally introduce a new object and avoid potential undefined behavior.
此分析基于 n4567,并使用了其中的节号。
§5.2.10/7: 对象指针类型的纯右值v
转换为对象指针类型“pointer to cv T”时,结果是 static_cast<cv T*>(static_cast<cv void*>(v))
.
因此,在这种情况下,reinterpret_cast<X*>(buffer)
与 static_cast<X *>(static_cast<void *>(buffer))
相同。这导致我们查看有关 static_cast
:
的相关部分
§5.2.9/13:“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2[的指针”类型的纯右值=39=] T”,其中 T 是一个对象类型,cv2 与 cv1[=39= 相同或更高的 cv 限定].空指针值转换为目标类型的空指针值。如果原始指针值表示一个字节在内存中的地址A
,并且A
满足T
的对齐要求,则得到的指针值表示与原始指针值相同的地址,即 A
.
我相信这足以说明原始引述是正确的——此转换给出了明确的结果。
至于寿命,这取决于你所说的寿命。强制转换创建了一个指针类型的新对象——一个临时对象,它的生命周期从强制转换所在的行开始,到超出范围时结束。如果您有两个有条件地发生的不同转换,则每个指针的生命周期从创建它的转换位置开始。
这些都不会影响提供底层存储的对象的生命周期,它仍然是 buffer
,并且具有完全相同的生命周期,无论您是否创建指针(相同类型或转换类型)是否存储到该存储空间。
没有 X
个物体,无论是活的还是其他的,所以假装有一个会导致未定义的行为。
[intro.object]/1详尽拼写对象创建时:
An object is created by a definition ([basic.def]), by a
new-expression ([expr.new]), when implicitly changing the active
member of a union ([class.union]), or when a temporary object is
created ([conv.rval], [class.temporary]).
随着P0137R1的采用,本段为"object"项的定义。
是否有 X
对象的定义?没有。有没有 new-expression?没有。有工会吗?否。您的代码中是否存在创建临时 X
对象的语言结构?号
无论 [basic.life] 所说的关于具有空初始化的对象的生命周期都是无关紧要的。要应用它,您首先必须有一个对象。你没有。
C++11也有大致相同的段落,但没有将其用作"object"的定义。尽管如此,解释是一样的。另一种解释 - 将 [basic.life] 视为在获得合适的存储后立即创建一个对象 - 意味着您正在创建薛定谔的对象 *,这与 N3337 [intro.object]/6 相矛盾:
Two objects that are not bit-fields may have the same address if one
is a subobject of the other, or if at least one is a base class
subobject of zero size and they are of different types; otherwise,
they shall have distinct addresses.
* 具有类型 T
的正确对齐和大小的存储根据定义是 [=34] 具有正确对齐和大小的存储=]所有其他类型,其大小和对齐要求等于或小于 T
。因此,该解释意味着同时获得存储会在所述存储中创建无限组具有不同类型的对象,所有对象都具有相同的地址。
基于p0593r6 I believe the code in the OP is valid and should be well defined. The new wording, based on the DR retroactively applied to all versions from C++98 inclusive, allows implicitly object creation as long as the created object is well defined (tautology is sometimes the rescue for complicated definitions), see § 6.7.2.11 Object model [intro.object]):
implicitly-created objects whose address is the address of the start
of the region of storage, and produce a pointer value that points to
that object, if that value would result in the program having defined
behavior [...]
另请参阅:
cppreference† 指出:
Objects with trivial default constructors can be created by using
reinterpret_cast
on any suitably aligned storage, e.g. on memory allocated withstd::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
或其他 pod,Y
怎么办? - 在这方面,C++11 和 C++1z 之间有什么变化吗?
†注意这是旧的link。措辞已针对此问题进行了更改。现在是:
Unlike in C, however, objects with trivial default constructors cannot be created by simply reinterpreting suitably aligned storage, such as memory allocated with
std::malloc
: placement-new is required to formally introduce a new object and avoid potential undefined behavior.
此分析基于 n4567,并使用了其中的节号。
§5.2.10/7: 对象指针类型的纯右值v
转换为对象指针类型“pointer to cv T”时,结果是 static_cast<cv T*>(static_cast<cv void*>(v))
.
因此,在这种情况下,reinterpret_cast<X*>(buffer)
与 static_cast<X *>(static_cast<void *>(buffer))
相同。这导致我们查看有关 static_cast
:
§5.2.9/13:“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2[的指针”类型的纯右值=39=] T”,其中 T 是一个对象类型,cv2 与 cv1[=39= 相同或更高的 cv 限定].空指针值转换为目标类型的空指针值。如果原始指针值表示一个字节在内存中的地址A
,并且A
满足T
的对齐要求,则得到的指针值表示与原始指针值相同的地址,即 A
.
我相信这足以说明原始引述是正确的——此转换给出了明确的结果。
至于寿命,这取决于你所说的寿命。强制转换创建了一个指针类型的新对象——一个临时对象,它的生命周期从强制转换所在的行开始,到超出范围时结束。如果您有两个有条件地发生的不同转换,则每个指针的生命周期从创建它的转换位置开始。
这些都不会影响提供底层存储的对象的生命周期,它仍然是 buffer
,并且具有完全相同的生命周期,无论您是否创建指针(相同类型或转换类型)是否存储到该存储空间。
没有 X
个物体,无论是活的还是其他的,所以假装有一个会导致未定义的行为。
[intro.object]/1详尽拼写对象创建时:
An object is created by a definition ([basic.def]), by a new-expression ([expr.new]), when implicitly changing the active member of a union ([class.union]), or when a temporary object is created ([conv.rval], [class.temporary]).
随着P0137R1的采用,本段为"object"项的定义。
是否有 X
对象的定义?没有。有没有 new-expression?没有。有工会吗?否。您的代码中是否存在创建临时 X
对象的语言结构?号
无论 [basic.life] 所说的关于具有空初始化的对象的生命周期都是无关紧要的。要应用它,您首先必须有一个对象。你没有。
C++11也有大致相同的段落,但没有将其用作"object"的定义。尽管如此,解释是一样的。另一种解释 - 将 [basic.life] 视为在获得合适的存储后立即创建一个对象 - 意味着您正在创建薛定谔的对象 *,这与 N3337 [intro.object]/6 相矛盾:
Two objects that are not bit-fields may have the same address if one is a subobject of the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they shall have distinct addresses.
* 具有类型 T
的正确对齐和大小的存储根据定义是 [=34] 具有正确对齐和大小的存储=]所有其他类型,其大小和对齐要求等于或小于 T
。因此,该解释意味着同时获得存储会在所述存储中创建无限组具有不同类型的对象,所有对象都具有相同的地址。
基于p0593r6 I believe the code in the OP is valid and should be well defined. The new wording, based on the DR retroactively applied to all versions from C++98 inclusive, allows implicitly object creation as long as the created object is well defined (tautology is sometimes the rescue for complicated definitions), see § 6.7.2.11 Object model [intro.object]):
implicitly-created objects whose address is the address of the start of the region of storage, and produce a pointer value that points to that object, if that value would result in the program having defined behavior [...]
另请参阅: