别名T *与字符*是允许的。 难道也让周围的其他方法?(Aliasing T* with cha

2019-06-18 08:17发布

注意:这个问题已经被重新命名,并减少,使其更有针对性和可读性。 大多数的评论指的是旧的文本。


根据该标准,不同类型的对象可以不共用相同的存储器位置。 所以这不会是合法的:

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?

Answer 1:

有些代码的是有问题的,由于涉及指针的转换。 请注意,在这些实例reinterpret_cast<T*>(e)具有的语义static_cast<T*>(static_cast<void*>(e))因为所涉及的类型是标准的布局。 (我其实建议您始终使用static_cast通过cv void*与存储的时候。)

标准的细读表明,一个指针转换或在T*假设,真的有实际的对象T*参与-这是很难在你的一些片断的履行,甚至作弊“感谢时所涉及的类型琐碎(以后会更多)。 这将是除了然而,因为点...

别名是不是指针转换。 这是概述了通常被称为“严格别名”的规则,从3.10的左值和右值[basic.lval]规则的C ++ 11文本:

10如果程序试图通过的以外的以下类型的行为是未定义的一个的glvalue访问对象的存储值:

  • 动态型的对象,
  • 动态对象的类型的CV-合格版本,
  • 类似类型(如在4.4定义)的动态类型的对象,
  • 一种类型,是有符号或对应的动态对象的类型无符号类型,
  • 一个类型在所述签名或对应于动态型的对象的CV-合格版本无符号类型,
  • 包括其元件或非静态数据成员之间的上述类型的一个集合体或联合类型(包括递归地,一个元件或子聚集的非静态数据成员或包含联合),
  • 一种类型是一个(可能CV修饰)基类型的动态型的对象的,
  • 一个char或unsigned char类型。

(这是在C ++ 03相同的条款和条款的第15段,用与例如“左值”文本一些细微的变化被用来代替“glvalue”,因为后者是一个C ++ 11的概念。)

在这些规则的光,让我们假设一个实现为我们提供了magic_cast<T*>(p)其中“以某种方式”的指针转换为另一种指针类型。 正常情况下应该 reinterpret_cast ,这将产生在一些情况下不确定的结果,但我已经在此之前解释也不是那么的指针标准布局类型。 然后,它显然是真的,所有的片段都是正确的(替代reinterpret_castmagic_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;

为了证明我的推理,假设这看似不同的片段:

// 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;

这段代码是精心构建的。 特别地,在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成为指针存储。

现在,由于intshort是微不足道的类型,我没有使用析构函数的显式调用。 我并不需要在构造函数中显式调用,或者(也就是说,通常的标准配置了新的呼叫中声明<new> )。 从3.8对象寿命[basic.life]:

1 [...] T类型的对象的生存期开始时:

  • 获得具有对于类型T的适当对准和尺寸存储,并
  • 如果对象具有不平凡的初始化,初始化完成。

结束类型T的对象的生存期时:

  • 如果T是具有一个非平凡的析构函数(12.4)的类类型,析构函数呼叫开始时,或
  • 该对象占据存储被重复使用或释放​​。

这意味着,我可以重写代码,使得折叠该中间变量后q ,我结束与原始片段。

请注意, p不能被折叠起来。 也就是说,以下是defintively不正确的:

alignas(alignment) char c[sizeof(int)];
*magic_cast<int*>(&c) = 42;
*magic_cast<short*>(&c) = 42;

如果我们假设一个int对象是与所述第二线路构成(平凡),那么必须意味着&c变为一个指向存储已被重复使用。 因此,第三行是不正确 - 虽然由于3.8 / 5-7,而不是由于重叠规则严格地说。

如果我们不认为,那么,第二行违反别名规则:我们在看什么书实际上是一个char c[sizeof(int)]经类型的glvalue对象int ,这是不是允许一个例外。 通过比较, *magic_cast<unsigned char>(&c) = 42; 将罚款(我们假设一个short对象在第三行平凡构造的)。

就像阿尔夫,我也建议使用存储时,你明确地使用了标准配置新的。 跳过销毁琐碎类型是好的,但在遇到当*some_magic_pointer = foo; 你很可能面临要么违反了3.8 / 5-7(无论是如何奇迹般地获得的指针)或的别名规则。 这意味着存储新的表达式的结果也一样,因为你很可能无法一次你的目标是构建重用魔术指针 - 同样是由于3.8 / 5-7。

读取对象的字节(这意味着使用charunsigned char )是好的但是,你甚至不使用reinterpret_cast或任何魔法都没有。 static_cast通过cv void*可以说是精细作业(虽然我觉得像标准可以使用一些更好的措辞存在)。



Answer 2:

这个也是:

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

这是不正确的。 混叠规则状态在何种情况下它是合法/非法通过不同类型的左值访问一个对象。 还有的是说你可以通过类型的指针可以访问任何对象的特定规则charunsigned char ,所以第一种情况下是正确的。 也就是说,A => B不必然意味着B =>答:您可以访问int通过一个指向char ,但你不能访问char通过指针来int


对于阿尔夫的好处:

如果一个程序试图通过的以外的以下类型的行为是未定义的一个glvalue访问对象的存储值:

  • 动态型的对象,
  • 动态对象的类型的CV-合格版本,
  • 类似类型(如在4.4定义)的动态类型的对象,
  • 一种类型,是有符号或对应的动态对象的类型无符号类型,
  • 一个类型在所述签名或对应于动态型的对象的CV-合格版本无符号类型,
  • 包括其元件或非静态数据成员之间的上述类型的一个集合体或联合类型(包括递归地,一个元件或子聚集的非静态数据成员或包含联合),
  • 一种类型是一个(可能CV修饰)基类型的动态型的对象的,
  • 一个char或unsigned char类型。


Answer 3:

关于有效性...

alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);

所述reinterpret_cast本身是OK还是没有,在生产有用的指针值,取决于编译器感。 并且在该示例的结果没有被使用,特别是,字符阵列不被访问。 因此,有没有更多的可以了解的例子可以说是-是:它只是取决于

但是,让我们考虑一个扩展版,它确实对别名规则触摸:

void foo( char* );

alignas(int) char c[sizeof( int )];

foo( c );
int* p = reinterpret_cast<int*>( c );
cout << *p << endl;

而且,我们只考虑在编译器保证了有用的指针值的情况下,一个将放置在指针对象相同的内存字节(这依赖于编译器的原因是标准的,在§5.2.10/ 7,只保证它为指针转化中, 类型是对准兼容,否则把它作为“未指定”(但随后,整个的§5.2.10与第9.2节/ 18有些不一致)。

现在,一种解释标准的§3.10/ 10,即所谓的“严格别名”条款(但要注意的是,标准没有用不完的术语“严格别名”),

如果一个程序试图通过的以外的以下类型的行为是未定义的一个glvalue访问对象的存储值:

  • 动态型的对象,
  • 动态对象的类型的CV-合格版本,
  • 类似类型(如在4.4定义)的动态类型的对象,
  • 一种类型,是有符号或对应的动态对象的类型无符号类型,
  • 一个类型在所述签名或对应于动态型的对象的CV-合格版本无符号类型,
  • 包括其元件或非静态数据成员之间的上述类型的一个集合体或联合类型(包括递归地,一个元件或子聚集的非静态数据成员或包含联合),
  • 一种类型是一个(可能CV修饰)基类型的动态型的对象的,
  • 一个charunsigned char类型。

是,由于它本身说,涉及动态类型驻留在所述对象的c字节。

有了这种解释,在读操作*p ,如果是OK foo已经把一个int对象那里,否则不是。 因此,在这种情况下,一个char阵列经由访问int*指针。 没有人在任何怀疑, 另一种方式是有效的:即使foo可能置于int对象的这些字节,你可以自由地访问该对象的序列char值,由§3.10/ 10的最后冲刺。

因此,与此(通常)解释,后foo已下int那里,我们可以访问它作为char对象,因此至少一个char对象指定存储器区域中存在c ; 我们可以访问它作为int ,所以至少一个int存在有也; 所以大卫在另一个答案断言该char对象不能作为访问int ,与此通常的解释是不相容的。

大卫的说法,也是最常用的布局新的不兼容。

至于什么其他可能的解释有,也许可能是大卫的说法,兼容的,好了,我想不出任何有意义。

所以在最后,只要神圣的标准而言,仅仅铸造自己一个T*指针数组实际有用与否取决于编译器,以及访问所指向的可能,是价值是否有效取决于什么当下。 特别是,想到了一个陷阱表示的int :你不想说你吹起来,如果一位模式恰巧是。 所以为了安全起见,你必须知道什么是在那里,位,该呼叫foo上述说明的编译器一般不知道 ,喜欢的G ++编译器的严格基于比对优化器通常不知道...



文章来源: Aliasing T* with char* is allowed. Is it also allowed the other way around?