用`char *`别名`T *`是允许的。 是否也允许其他方式?
注意:这个问题已经被重新命名和缩减,使其更加专注和可读。 大多数评论都是指旧文本。
–
根据标准,不同types的对象可能不共享相同的内存位置。 所以这不合法:
std::array<short, 4> shorts; int* i = reinterpret_cast<int*>(shorts.data()); // Not OK
但是,标准允许这个规则的一个例外:任何对象都可以通过指向char
或unsigned char
的指针来访问:
int i = 0; char * c = reinterpret_cast<char*>(&i); // OK
但是,我不清楚这是否也是相反的。 例如:
char * c = read_socket(...); unsigned * u = reinterpret_cast<unsigned*>(c); // huh?
由于涉及指针转换,您的一些代码是有问题的。 请记住,在这些情况下, reinterpret_cast<T*>(e)
具有static_cast<T*>(static_cast<void*>(e))
的语义,因为涉及的types是标准布局。 (事实上,我build议你在处理存储时总是使用cv void*
来使用static_cast
。)
对标准的仔细阅读表明,在T*
的指针转换过程中,假设实际上有一个实际的对象T*
,在一些片段中很难实现,即使是“作弊”感谢所涉及的types的琐碎(稍后更多)。 这将是除了点之外,因为…
别名与指针转换无关。 这是C ++ 11文本,概述了通常被称为“严格别名”规则的规则,从3.10左值和右值[basic.lval]:
10如果程序试图通过以下types之一的glvalue访问对象的存储值,则行为是未定义的:
- 对象的dynamictypes,
- 该对象的dynamictypes的cv限定版本,
- types(与4.4中定义的)types的对象的dynamictypes,
- types是与对象的dynamictypes对应的有符号或无符号types,
- types是对应于对象的dynamictypes的cv限定版本的有符号或无符号types,
- 包含其元素之一的上述types或非静态数据成员(包括recursion地包含子集或包含的联合的元素或非静态数据成员)的集合或联合types,
- 作为对象的dynamictypes的(可能是cv合格的)基类types的types,
- 一个字符或无符号的字符types。
(这是C ++ 03中同一子句和子条款的第15段,在文本中有一些细微的变化,例如使用“左值”代替“glvalue”,因为后者是C ++ 11的概念。
根据这些规则,让我们假设一个实现为我们提供了magic_cast<T*>(p)
,它以某种方式将指针转换为另一个指针types。 通常情况下,这将是reinterpret_cast
,在某些情况下会产生未指定的结果,但正如我之前解释的,对于指向标准布局types的指针而言,情况并非如此。 那么很明显,你所有的片段都是正确的(用magic_cast
代替reinterpret_cast
),因为magic_cast的结果没有任何glvalues参与。
这是一个看起来错误地使用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;
为了certificate我的推理,假设这个表面上不同的片段:
// 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;
这个片段是精心构build的。 特别是在new (&c) int;
即使c
因为3.8对象生命期[basic.life]的第5节中规定的规则被销毁,我也可以使用&c
。 第6段给出了与存储引用非常类似的规则,第7段解释了一旦存储被重用后,用于引用对象的variables,指针和引用发生了什么 – 我将统称为3.8 / 5- 7。
在这个例子中, &c
被隐式转换为void*
,这是正确使用尚未被重用的存储器的指针之一。 类似地, p
是在构造新的int
之前从&c
获得的。 其定义也许可以在c
的破坏之后被移动,取决于实施魔法有多深,但肯定不是在整体build设之后:第7段将适用,这不是允许的情况之一。 short
对象的构build也依赖于成为存储的指针。
现在,因为int
和short
是微不足道的types,所以我不必使用对析构函数的显式调用。 我不需要对构造函数进行显式调用(也就是说,调用通常在<new>
声明的Standard placement new)。 从3.8对象生命[basic.life]:
1 […]typesT的对象的生命周期开始于:
- 存储与T型适当的alignment和大小,并获得
- 如果对象具有非平凡的初始化,则其初始化完成。
typesT的对象的生命周期在以下情况下结束:
- 如果T是一个具有非平凡析构函数的类types(12.4),则析构函数调用开始,或者
- 对象占用的存储空间被重用或释放。
这意味着我可以重写代码,以便在折叠中间variablesq
,最终得到原始代码片段。
请注意, p
不能折叠。 也就是说,以下是肯定不正确的:
alignas(alignment) char c[sizeof(int)]; *magic_cast<int*>(&c) = 42; *magic_cast<short*>(&c) = 42;
如果我们假设一个int
对象是(第三行)用第二行构造的,那么这必须意味着&c
变成了一个指向已被重用的存储的指针。 因此,第三行是不正确的 – 虽然由于3.8 / 5-7而不是由于严格的锯齿规则。
如果我们不假设,那么第二行违反了别名规则:我们通过int
types的glvalue读取实际上是char c[sizeof(int)]
对象的东西,这不是允许的例外。 相比之下, *magic_cast<unsigned char>(&c) = 42;
会没事的(我们假设一个short
对象是在第三行上构造的)。
就像Alf一样,我还build议您在使用存储时明确使用“标准”展示位置。 跳过琐碎types的破坏是好的,但遇到*some_magic_pointer = foo;
您很有可能面临违反3.8 / 5-7(无论指针是如何神奇的)或别名规则。 这意味着也要存储新expression式的结果,因为一旦你的对象被构造,你很可能不能再次使用这个魔法指针了 – 由于3.8 / 5-7再次。
读取对象的字节(这意味着使用char
或unsigned char
)是好的,你甚至不使用reinterpret_cast
或任何魔术。 static_cast
通过cv void*
对于工作来说可以说是很好的(尽pipe我觉得标准可以在那里使用一些更好的措辞)。
这个也是:
// valid: char -> type alignas(int) char c[sizeof(int)]; int * i = reinterpret_cast<int*>(c);
这是不正确的。 别名规则规定在哪种情况下通过不同types的左值访问对象是合法/非法的。 有一个特定的规则说,你可以通过char
或unsigned char
types的指针访问任何对象,所以第一种情况是正确的。 也就是说,A => B并不一定意味着B => A.您可以通过指向char
的指针访问int
,但不能通过指向int
的指针访问char
。
为了Alf的利益:
如果程序试图通过以下types之一的glvalue来访问对象的存储值,则行为是未定义的:
- 对象的dynamictypes,
- 该对象的dynamictypes的cv限定版本,
- types(与4.4中定义的)types的对象的dynamictypes,
- types是与对象的dynamictypes对应的有符号或无符号types,
- types是对应于对象的dynamictypes的cv限定版本的有符号或无符号types,
- 在其元素或非静态数据成员(包括recursion地包括子集或包含的联合的元素或非静态数据成员)中包括上述types之一的集合或联合types,
- 作为对象的dynamictypes的(可能是cv合格的)基类types的types,
- 一个字符或无符号的字符types。
关于…的有效性
alignas(int) char c[sizeof(int)]; int * i = reinterpret_cast<int*>(c);
根据编译器的不同, reinterpret_cast
本身是否可以生成有用的指针值。 而在这个例子中结果没有被使用,特别是字符数组没有被访问。 所以现在没有什么可以说的例子了,这只是依赖 。
但是让我们考虑一个扩展版本,它涉及到别名规则:
void foo( char* ); alignas(int) char c[sizeof( int )]; foo( c ); int* p = reinterpret_cast<int*>( c ); cout << *p << endl;
我们只考虑编译器保证一个有用的指针值的情况,一个将指针放在同一个内存字节中的原因(这取决于编译器的原因是仅在§5.2.10/ 7中的标准)保证它在typesalignment兼容的指针转换中保持不变,否则将其保留为“未指定”(但是,那么整个§5.2.10与§9.2/ 18有些不一致)。
现在,对标准§3.10/ 10所谓的“严格别名”条款的一种解释(但是请注意,该标准没有使用“严格别名”一词)
如果程序试图通过以下types之一的glvalue来访问对象的存储值,则行为是未定义的:
- 对象的dynamictypes,
- 该对象的dynamictypes的cv限定版本,
- types(与4.4中定义的)types的对象的dynamictypes,
- types是与对象的dynamictypes对应的有符号或无符号types,
- types是对应于对象的dynamictypes的cv限定版本的有符号或无符号types,
- 在其元素或非静态数据成员(包括recursion地包括子集或包含的联合的元素或非静态数据成员)中包括上述types之一的集合或联合types,
- 作为对象的dynamictypes的(可能是cv合格的)基类types的types,
- 一个
char
或unsigned
char
types。
就像它本身所说的那样,它涉及到驻留在c
字节中的对象的dynamictypes 。
有了这个解释,如果foo
在那里放置了一个int
对象,那么在*p
上的读操作是OK的,否则不行。 所以在这种情况下, char
数组通过int*
指针访问。 没有人怀疑另一种方式是有效的:尽pipefoo
可能已经在这些字节中放置了一个int
对象,但是可以在§3.10/ 10的最后一行中以char
值的顺序自由地访问该对象。
因此,通过这种(通常的)解释,在foo
放置一个int
之后,我们可以将其作为char
对象来访问,所以在名为c
的内存区域中至less存在一个char
对象。 我们可以像int
那样访问它,所以至less那个int
也存在那里; 所以David在另一个答案中断言 char
对象不能作为int
来访问,这与这个通常的解释是不相容的。
大卫的说法也与最常见的放置新方法不兼容。
关于还有什么可能的解释呢,这也许可以和大卫的说法一致,好吧,我想不出有什么意思。
因此,就神圣标准而言,仅仅将自己的T*
指针指向数组实际上是有用的或者不依赖于编译器,并且访问被指向的可能值是有效的或者不依赖于什么当下。 特别是想一想int
的一个陷阱表示:如果bitpattern碰巧是这样的话,那么你不会希望对你吹嘘。 所以为了安全起见,你必须知道里面有什么,这些位,以及上面foo
的调用,说明编译器通常可以不知道 g ++编译器的严格基于alignment的优化器通常不知道…