这是否可能导致未定义的行为?
uint8_t storage[4];
// We assume storage is properly aligned here.
int32_t* intPtr = new((void*)storage) int32_t(4);
// I know this is ok:
int32_t value1 = *intPtr;
*intPtr = 5;
// But can one of the following cause UB?
int32_t value2 = reinterpret_cast<int32_t*>(storage)[0];
reinterpret_cast<int32_t*>(storage)[0] = 5;
char
有严格别名的特殊规则。如果我使用char
代替uint8_t
,它仍然是未定义的行为吗?还有什么变化?
正如成员DeadMG所指出的,reinterpret_cast
是依赖于实现的。如果我使用C风格的演员(int32_t*)storage
代替,会发生什么变化?
答案 0 :(得分:7)
当将别名考虑因素带入其中时,placement new返回的指针可能与任何其他指针一样引起UB。您有责任确保您放置对象的内存不会被任何不应该存在的内容别名。
在这种情况下,您不能假设uint8_t
是char
的别名,因此应用了特殊的别名规则。另外,使用uint8_t
而不是char
的数组是没有意义的,因为sizeof()
是char
而不是uint8_t
。你必须自己计算尺寸。
此外,reinterpret_cast
的效果完全是实现定义的,因此代码当然没有明确的含义。
要实现低级别令人不快的内存黑客攻击,原始内存只需要char*
,void*
和T*
别名,其中T
是最终目的地在这种情况下键入int
,以及从T*
获得的任何其他内容,例如if T
是派生类,并将该派生类指针转换为指向base的指针。其他任何事情都违反严格的别名和你好的鼻子恶魔。
答案 1 :(得分:6)
您使用常规展示位置的版本确实很好。
§§3.8/ 1和3.8 / 4有一个解释 1 ,其中琐碎类型的对象能够根据需要“消失”和“出现”。这不是允许忽略别名规则的免费通行证,请注意:
std::uint16_t storage[2];
static_assert( /* std::uint16_t is not a character type */ );
static_assert( /* storage is properly aligned for our purposes */ );
auto read = *reinterpret_cast<std::uint32_t*>(&storage);
// At this point either we’re attempting to read the value of an
// std::uint16_t object through an std::uint32_t glvalue, a clear
// strict aliasing violation;
// or we’re reading the indeterminate value of a new std::uint32_t
// object freshly constructed in the same storage without effort
// on our part
另一方面,如果您在第二个片段中交换了演员阵容(即重新解释并先写入),那么您也不完全安全。在解释下,您可以证明写入发生在隐式重用存储的新std::uint32_t
对象上,后续读取的形式为
auto value2 = *reinterpret_cast<int32_t*>(storage);
和§3.8/ 5说(强调我和极其相关):
[...]在对象的生命周期结束后在对象占用的存储之前重新使用或释放,任何引用该对象所在或将被定位的存储位置的指针可以使用但仅限于有限的方式。 [...]这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针类型为
void*
一样,是明确定义的。
§3.8/ 6是相同的,但是以参考/ glvalue形式(可能更具相关性,因为我们在这里重复使用名称而不是指针,但是段落在上下文之外更难理解)。另见§3.8/ 7,它给出了一些我认为不适用的有限余地。
为了简单起见,剩下的问题是:
T object;
object.~T();
new (&object) U_thats_really_different_from_T;
&object; // Is this allowed? What does it mean?
static_cast<void*>(&object); // Is this?
如果存储类型恰好涉及普通或无符号字符类型(例如,您的storage
确实有类型unsigned char[4]
),那么我会说你有理由证明形成对新对象存储的指针/引用(可能稍后重新解释)。参见例如¶¶5和6再次,它有一个用于形成指针/引用/ glvalue的显式转义子句和§1.8 C ++对象模型,它描述了一个对象如何涉及一个组成的字节数组。控制指针转换的规则应该是直截了当且无可争议的(至少通过比较......)。
1 :很难判断这种解释在社区中得到了多少 - 我在Boost邮件列表上看到了这一点,对此有一些怀疑