Sta*_*ked 27 c++ strict-aliasing language-lawyer type-punning
注意:此问题已重命名并缩小,以使其更具针对性和可读性.大多数评论都涉及旧文本.
根据标准,不同类型的对象可能不共享相同的存储位置.所以这不合法:
std::array<short, 4> shorts;
int* i = reinterpret_cast<int*>(shorts.data()); // Not OK
Run Code Online (Sandbox Code Playgroud)
但是,该标准允许此规则的例外:可以通过指向char
或的指针访问任何对象unsigned char
:
int i = 0;
char * c = reinterpret_cast<char*>(&i); // OK
Run Code Online (Sandbox Code Playgroud)
但是,我不清楚这是否也允许反过来.例如:
char * c = read_socket(...);
unsigned * u = reinterpret_cast<unsigned*>(c); // huh?
Run Code Online (Sandbox Code Playgroud)
Luc*_*ton 23
由于涉及指针转换,您的一些代码是有问题的.请记住,在这些实例中reinterpret_cast<T*>(e)
具有语义,static_cast<T*>(static_cast<void*>(e))
因为涉及的类型是标准布局.(事实上我建议你在处理存储时总是使用static_cast
via cv void*
.)
仔细阅读标准表明,在一个指针转换到或来自T*
它时,假设确实存在一个实际的对象T*
- 这在你的一些片段中难以实现,即使在"作弊"时也要感谢各种类型的类型涉及(稍后会详述).那将是除了这一点,因为......
别名与指针转换无关.这是C++ 11文本,它概述了3.10 Lvalues和rvalues [basic.lval]中通常称为"严格别名"规则的规则:
10如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 一个cv限定版本的动态类型的对象,
- 与对象的动态类型类似的类型(如4.4中所定义),
- 与对象的动态类型对应的有符号或无符号类型的类型,
- 一种类型,是有符号或无符号类型,对应于对象动态类型的cv限定版本,
- 聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(递归地,包括子聚合或包含联合的元素或非静态数据成员),
- 一个类型,它是对象动态类型的(可能是cv限定的)基类类型,
- char或unsigned char类型.
(这是C++ 03中同一条款和子条款的第15段,文本中有一些细微的变化,例如使用'lvalue'而不是'glvalue',因为后者是C++ 11的概念.)
根据这些规则,让我们假设一个实现为我们提供magic_cast<T*>(p)
了"以某种方式"将指针转换为另一个指针类型.通常这会是reinterpret_cast
,在某些情况下产生未指定的结果,但正如我之前所解释的那样,对于标准布局类型的指针并非如此.然后,它显然是真的,所有的片段都是正确的(替换reinterpret_cast
用magic_cast
),因为没有glvalues与结果任何参与magic_cast
.
这是一个看似错误使用的片段magic_cast
,但我认为这是正确的:
// assume constexpr max
constexpr auto alignment = max(alignof(int), alignof(short));
alignas(alignment) char c[sizeof(int)];
// I'm assuming here that the OP really meant to use &c and not c
// this is, however, inconsequential
auto p = magic_cast<int*>(&c);
*p = 42;
*magic_cast<short*>(p) = 42;
Run Code Online (Sandbox Code Playgroud)
为了证明我的推理,假设这个表面上不同的片段:
// alignment same as before
alignas(alignment) char c[sizeof(int)];
auto p = magic_cast<int*>(&c);
// end lifetime of c
c.~decltype(c)();
// reuse storage to construct new int object
new (&c) int;
*p = 42;
auto q = magic_cast<short*>(p);
// end lifetime of int object
p->~decltype(0)();
// reuse storage again
new (p) short;
*q = 42;
Run Code Online (Sandbox Code Playgroud)
这个片段是精心构造的.特别是,在new (&c) int;
我被允许使用&c
,即使c
被摧毁,由于在3.8对象生存[basic.life]第5款中规定的规则.它的第6段提供了与存储引用非常相似的规则,第7段解释了一旦存储被重用后用于引用对象的变量,指针和引用会发生什么 - 我将统称为3.8/5- 7.
在这个实例&c
中(隐式)转换为void*
,这是正确使用尚未重用的存储指针之一.类似地p
从构造&c
新的之前获得int
.c
根据实施魔法的深度,它的定义可能会被移动到,但肯定不会在int
构造之后:第7段将适用,这不是允许的情况之一.short
对象的构造还依赖于p
成为存储的指针.
现在,因为int
并且short
是微不足道的类型,我不必使用对析构函数的显式调用.我也不需要显式调用构造函数(也就是说,调用通常的标准放置新的声明<new>
).从3.8对象生命周期[basic.life]:
1 [...] T类对象的生命周期始于:
- 获得具有适当对齐和T型尺寸的存储,并且
- 如果对象具有非平凡的初始化,则其初始化完成.
类型T的对象的生命周期在以下情况下结束:
- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
- 对象占用的存储器被重用或释放.
这意味着我可以重写代码,以便在折叠中间变量之后q
,我最终得到原始代码段.
请注意,p
不能折叠.也就是说,以下内容肯定是不正确的:
alignas(alignment) char c[sizeof(int)];
*magic_cast<int*>(&c) = 42;
*magic_cast<short*>(&c) = 42;
Run Code Online (Sandbox Code Playgroud)
如果我们假设一个int
对象(通常)用第二行构造,那么这必然意味着&c
成为一个指向已经重用的存储的指针.因此第三行是不正确的 - 尽管由于3.8/5-7并且严格来说不是由于混叠规则.
如果我们不认为,那么,第二行是违反别名规则:我们在看什么书实际上是一个char c[sizeof(int)]
通过类型的glvalue对象int
,这是不是在允许的例外之一.相比之下,没问题*magic_cast<unsigned char>(&c) = 42;
(我们假设short
在第三行上简单地构造了一个对象).
就像Alf一样,我还建议您在使用存储时明确使用标准展示位置.跳过破坏琐碎的类型是很好的,但遇到*some_magic_pointer = foo;
你很可能面临违反3.8/5-7(无论多么神奇地获得指针)或别名规则.这意味着也要存储新表达式的结果,因为一旦你的对象被构造,你很可能无法重用魔术指针 - 由于3.8/5-7再次.
读取对象的字节(这意味着使用char
或者unsigned char
)很好,但你甚至根本不使用reinterpret_cast
任何魔法.static_cast
via cv void*
对于这项工作来说可以说是好的(虽然我觉得标准可以在那里使用更好的措辞).
这个也是:
// valid: char -> type
alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);
Run Code Online (Sandbox Code Playgroud)
这是不正确的.别名规则声明在哪种情况下通过不同类型的左值访问对象是合法/非法的.还有的是说你可以通过类型的指针可以访问任何对象的特定规则char
或者unsigned char
,所以第一种情况下是正确的.也就是说,A => B并不一定意味着B => A.您可以int
通过指针访问char
,但是您无法char
通过指针访问int
.
为了Alf的利益:
如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 一个cv限定版本的动态类型的对象,
- 与对象的动态类型类似的类型(如4.4中所定义),
- 与对象的动态类型对应的有符号或无符号类型的类型,
- 一种类型,是有符号或无符号类型,对应于对象动态类型的cv限定版本,
- 聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(递归地,包括子聚合或包含联合的元素或非静态数据成员),
- 一个类型,它是对象动态类型的(可能是cv限定的)基类类型,
- char或unsigned char类型.