用`char *`别名`T *`是允许的。 是否也允许其他方式?

注意:这个问题已经被重新命名和缩减,使其更加专注和可读。 大多数评论都是指旧文本。

根据标准,不同types的对象可能不共享相同的内存位置。 所以这不合法:

std::array<short, 4> shorts; int* i = reinterpret_cast<int*>(shorts.data()); // Not OK 

但是,标准允许这个规则的一个例外:任何对象都可以通过指向charunsigned 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也依赖于成为存储的指针。

现在,因为intshort是微不足道的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而不是由于严格的锯齿规则。

如果我们不假设,那么第二行违反了别名规则:我们通过inttypes的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再次。

读取对象的字节(这意味着使用charunsigned char )是好的,你甚至不使用reinterpret_cast或任何魔术。 static_cast通过cv void*对于工作来说可以说是很好的(尽pipe我觉得标准可以在那里使用一些更好的措辞)。

这个也是:

 // valid: char -> type alignas(int) char c[sizeof(int)]; int * i = reinterpret_cast<int*>(c); 

这是不正确的。 别名规则规定在哪种情况下通过不同types的左值访问对象是合法/非法的。 有一个特定的规则说,你可以通过charunsigned chartypes的指针访问任何对象,所以第一种情况是正确的。 也就是说,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,
  • 一个charunsigned chartypes。

就像它本身所说的那样,它涉及到驻留在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的优化器通常不知道…