将char数组强制转换为对象指针-这是UB吗? [英] Casting a char array to an object pointer - is this UB?
问题描述
最近我看到一个类似的类,该类用于按需"构造对象,而由于各种原因而不必使用动态内存分配.
I recently saw a class like this that was used to construct objects "on-demand" without having to use dynamic memory allocation for various reasons.
#include <cassert>
template<typename T>
class StaticObject
{
public:
StaticObject() : constructed_(false)
{
}
~StaticObject()
{
if (constructed_)
((T*)object_)->~T();
}
void construct()
{
assert(!constructed_);
new ((T*)object_) T;
constructed_ = true;
}
T& operator*()
{
assert(constructed_);
return *((T*)object_);
}
const T& operator*() const
{
assert(constructed_);
return *((T*)object_);
}
private:
bool constructed_;
alignas(alignof(T)) char object_[sizeof(T)];
};
这是代码,即将正确对齐的char数组强制转换为对象指针,是C ++ 14标准认为未定义的行为吗?还是完全可以?
Is this code, namely the casting of a properly aligned char array to an object pointer, considered undefined behavior by the C++14 standard or is it completely fine?
推荐答案
该程序在技术上具有未定义的行为,尽管它可能适用于大多数实现.问题是,即使char*
指针表示用于以下操作的第一个字节的地址,也不能保证从char*
到T*
的强制转换会导致有效的指针指向由placement new创建的T
对象. T
对象的存储空间.
This program technically has undefined behavior, although it's likely to work on most implementations. The issue is that a cast from char*
to T*
is not guaranteed to result in a valid pointer to the T
object created by placement new, even though the char*
pointer represents the address of the first byte used for storage for the T
object.
与布局兼容的类型的指针必须具有相同的值表示和对齐要求([basic.align]).
Pointers to layout-compatible types shall have the same value representation and alignment requirements ([basic.align]).
通常,T
与char
或alignas(T) char[sizeof(T)]
在布局上不兼容,因此不要求指针T*
与指针char*
或void*
具有相同的值表示形式
In general, T
will not be layout-compatible with char
or with alignas(T) char[sizeof(T)]
, so there's no requirement that a pointer T*
has the same value representation as a pointer char*
or void*
.
如果满足以下条件,则两个对象 a 和 b 是指针可互换的:
Two objects a and b are pointer-interconvertible if:
-
它们是同一对象,或者
they are the same object, or
一个是联合对象,另一个是该对象的非静态数据成员([class.union]),或者
one is a union object and the other is a non-static data member of that object ([class.union]), or
一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有非静态数据成员,则是该对象的任何基类子对象( [class.mem]),或
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or
存在一个对象 c ,这样 a 和 c 是指针可互换的,而 c 和 b 是指针可互换的.
there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.
如果两个对象是指针可互换的,则它们具有相同的地址,并且可以通过reinterpret_cast
从指向另一个的指针获得指向一个的指针. [注意:数组对象及其第一个元素即使指针具有相同的地址,也不能指针可相互转换. — 尾注]
If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast
. [ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]
[另外: DR 2287 已更改C ++ 17发布后的第二个项目符号中,从标准布局联合"到联合".但这并不影响该程序.]
[Aside: DR 2287 changed "standard-layout union" to "union" in the second bullet after the publication of C++17. But that doesn't affect this program.]
由布局new创建的T
对象不能与object_
或object_[0]
进行指针互换.注释暗示这可能是演员的问题...
The T
object created by the placement new is not pointer-interconvertible with object_
or with object_[0]
. And the note hints that this might be a problem for casts...
对于C样式转换((T*)object_)
,我们需要查看 [expr .cast]/4 :
For the C-style cast ((T*)object_)
, we need to see [expr.cast]/4:
由
-
a
const_cast
,
a static_cast
,
a static_cast
,后跟const_cast
,
a reinterpret_cast
或
a reinterpret_cast
后跟const_cast
可以使用显式类型转换的强制转换符号来执行....
can be performed using the cast notation of explicit type conversion....
如果可以采用以上列出的一种以上的方式来解释转换,那么即使该解释产生的转换格式不正确,也要使用列表中最先出现的解释.
If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed.
除非T
是char
或具有cv资格的char
,否则它实际上是reinterpret_cast
,因此接下来我们看一下
Unless T
is char
or cv-qualified char
, this will effectively be a reinterpret_cast
, so next we look at [expr.reinterpret.cast]/7:
可以将对象指针显式转换为其他类型的对象指针.将对象指针类型的prvalue
v
转换为对象指针类型指向 cvT
的指针"时,结果为static_cast<
cvT*>(static_cast<
cvvoid*>(v))
.
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue
v
of object pointer type is converted to the object pointer type "pointer to cvT
", the result isstatic_cast<
cvT*>(static_cast<
cvvoid*>(v))
.
所以首先我们有一个static_cast
从char*
到void*
,它执行
So first we have a static_cast
from char*
to void*
, which does the standard conversion described in [conv.ptr]/2:
" cv
T
的指针"类型的prvalue,其中T
是对象类型,可以转换为" cvvoid
".指针值([basic.compound])在此转换后保持不变.
A prvalue of type "pointer to cv
T
", whereT
is an object type, can be converted to a prvalue of type "pointer to cvvoid
". The pointer value ([basic.compound]) is unchanged by this conversion.
其后是从void*
到T*
的static_cast
,如 [expr.static.cast]/13 :
可以将指向 cv1
void
的指针"的prvalue转换为指向 cv2T
的指针"的prvalue,其中T
是对象类型,并且 cv2 与 cv1 具有相同的cv限定,或具有更高的cv限定.如果原始指针值表示内存中字节的地址A
,并且A
不满足T
的对齐要求,则未指定结果指针值.否则,如果原始指针值指向对象 a ,并且存在类型为T
的对象 b (忽略cv限定),则该指针可与 a ,结果是指向 b 的指针.否则,转换后指针值将保持不变.
A prvalue of type "pointer to cv1
void
" can be converted to a prvalue of type "pointer to cv2T
", whereT
is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the addressA
of a byte in memory andA
does not satisfy the alignment requirement ofT
, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of typeT
(ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.
如前所述,类型T
的对象不能与object_[0]
进行指针互换,因此该语句不适用,并且不能保证结果T*
指向T
对象!我们只剩下说指针值未更改"的句子,但是如果char*
和T*
指针的值表示形式太不同,这可能不是我们想要的结果.
As already noted, the object of type T
is not pointer-interconvertible with object_[0]
, so that sentence does not apply, and there's no guarantee that the result T*
points at the T
object! We're left with the sentence saying "the pointer value is unchanged", but this might not be the result we want if the value representations for char*
and T*
pointers are too different.
可以使用union
实现该类的标准兼容版本:
A Standard-compliant version of this class could be implemented using a union
:
template<typename T>
class StaticObject
{
public:
StaticObject() : constructed_(false), dummy_(0) {}
~StaticObject()
{
if (constructed_)
object_.~T();
}
StaticObject(const StaticObject&) = delete; // or implement
StaticObject& operator=(const StaticObject&) = delete; // or implement
void construct()
{
assert(!constructed_);
new(&object_) T;
constructed_ = true;
}
T& operator*()
{
assert(constructed_);
return object_;
}
const T& operator*() const
{
assert(constructed_);
return object_;
}
private:
bool constructed_;
union {
unsigned char dummy_;
T object_;
}
};
甚至更好,因为此类实际上是在尝试实现optional
,因此只需使用 std::optional
(如果有)或
Or even better, since this class is essentially attempting to implement an optional
, just use std::optional
if you have it or boost::optional
if you don't.
这篇关于将char数组强制转换为对象指针-这是UB吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!