【问题标题】:Strict aliasing rule and 'char *' pointers严格的别名规则和 'char *' 指针
【发布时间】:2014-07-13 22:22:47
【问题描述】:

What is the strict aliasing rule? 的公认答案提到您可以使用 char * 为另一种类型设置别名,但不能使用其他方式。

这对我来说没有意义——如果我们有两个指针,一个 char * 类型和另一个 struct something * 类型指向同一个位置,第一个别名怎么可能是第二个但第二个不是第一个别名吗?

【问题讨论】:

  • 您可以通过char * 读取T,但您不能通过T * 读取任意char 缓冲区。
  • 这是一条规则,仅此而已...基本上允许编译器进行更多优化(与restrict一样)...但恕我直言,编译器家伙也很懒...
  • 这是 C/C++ 工作方式不同的地方之一
  • @user3489275: 等等,也许我的引用不清楚 - 转换指针是可以的,但不能通过 char 指针写入对象的内存。 IE。当您写入内存时,会使原始对象无效。通过 char 指针读取对象的底层表示的字节非常好(实际上这就是任何 I/O 的工作方式)。
  • @KerrekSB:不是使用非平凡析构函数重用对象的内存(并因此终止生命周期)的 UB。尽管听起来很奇怪,但标准明确指出,只有在程序依赖于析构函数的副作用时,这才是未定义的。另外,我不认为通过char* 结束 对象的生命周期。使用placement new,当然,只是通过指针写...不太确定。

标签: c++ c strict-aliasing


【解决方案1】:

如果我们有两个指针,一个 char * 类型的指针和另一个 struct something * 类型的指针指向同一个位置,那么第一个指针怎么可能给第二个取别名,而第二个不给第一个取别名呢?

确实如此,但这不是重点。

关键是,如果您有一个或多个struct somethings,那么您可以使用char* 来读取它们的组成字节,但如果您有一个或多个chars,那么您可能不会使用@987654326 @阅读它们。

【讨论】:

  • 之所以允许一个 char* 给另一个类型起别名,仅仅是因为它提供了一种非常简单的方式来序列化一个结构体,并且是一种常用的模式。
  • 现在 OP 也知道了。
  • 定义“你有一个或多个字符”
  • @curiousguy:有什么不清楚的地方? char buf[sizeof(something)] = {}; something* ptr = reinterpret_cast<something*>(&buf[0]); // invalid
  • “拥有”的整个概念。一个字符是一个字节。每个对象表示都是一堆字节或字符。你总是有一个或多个字符。
【解决方案2】:

参考答案中的措辞略有错误,所以让我们先解决这个问题: 一个 object 永远不会为另一个 object 起别名,但两个 pointers 可以“别名”同一个对象(意味着指针指向同一个内存位置 -正如 MM 指出的那样,这仍然不是 100% 正确的措辞,但你明白了)。此外,标准本身(据我所知)实际上根本没有谈论严格的别名,而只是给出了规则,通过哪些表达式可以访问或不访问对象。像'-fno-strict-aliasing'这样的编译器标志告诉编译器它是否可以假设程序员遵循这些规则(因此它可以基于该假设执行优化)。

现在回答您的问题:任何 object 都可以通过 pointer 访问 char,但 char object (尤其是char 数组)可能无法通过大多数其他指针类型访问。 基于此,编译器可以/必须做出以下假设:

  1. 如果不知道实际对象本身的类型,char*T* 可以始终指向同一个对象(互为别名)-> 对称关系。
  2. 如果T1T2不是“相关的”并且不是char,那么T1*T2*可能永远不会指向同一个对象->对称关系
  3. char* 可能指向charT 对象
  4. T* 不能指向char 对象 -> a对称关系

我相信,关于通过指针访问 objectasymmetric 规则背后的主要理由是 char 数组可能不满足对齐要求,例如一个int

因此,即使没有基于严格别名规则的编译器优化,例如将int 写入地址为 0x1,0x2,0x3,0x4 的 4 字节 char 数组的位置 - 在最好的情况下 - 会导致性能下降 - 在最坏的情况下 - 访问不同的内存位置,因为 CPU 指令在写入 4 字节值时可能会忽略最低两个地址位(因此这里可能会导致写入 0x0,0x1,0x2 和 0x3)。

还请注意,“相关”的含义因语言而异(在 C 和 C++ 之间),但这与您的问题无关。

【讨论】:

  • 对齐不是严格别名规则的理由,更不用说主要规则了。这是一个正交问题。别名规则的原因是启用优化。 (有时称为 TBAA - 基于类型的混叠分析)。此外,该规则也不是关于彼此别名的指针。它是关于一个对象的左值别名。
  • @M.M:我没有说这是严格别名规则本身的主要原因,但为什么例如char* 可能指向 intint* 可能不指向 char 数组。我更正了我的帖子,所以它不再谈论两个相互混淆的指针。也许你可以再看看?
  • 也取出“主要理由”开头的段落;对齐不是别名规则的基本原理
  • @M.M:对不起,我在这部分碰巧不同意你的观点,下面的段落解释了为什么我认为这是一个有效的理性。再说一次,我不是说,严格别名规则本身是合理的,而是它的特定部分。
  • 别名规则甚至适用于对齐的内存。
【解决方案3】:

如果我们有两个指针,一个是char * 类型,另一个是struct something * 类型的指针指向同一个位置,怎么可能第一个给第二个起别名而第二个没有给第一个起别名?

指针之间不存在别名;那是对语言的草率使用。别名是指使用 lvalue 访问不同类型的 object。 (取消引用指针会产生一个左值)。

在您的示例中,重要的是被别名对象的类型。举一个具体的例子,假设对象是double。通过取消引用指向双精度的char * 来访问double 很好,因为严格的别名规则允许这样做。但是,不允许通过取消引用 struct something * 来访问 double(除非可以说结构以 double! 开头)。

如果编译器正在查看一个采用 char *struct something * 的函数,并且它没有可用的有关所指向对象的信息(这实际上不太可能,因为别名传递是在整个程序中完成的优化阶段);那么它必须考虑到对象实际上可能是struct something * 的可能性,因此无法在此函数内部进行优化。

【讨论】:

  • 取消引用结构没有效果,它不读取内存。
  • @curiousguy 通过取消引用指向结构的指针产生的左值可用于读取或写入内存。例如struct something *x = whatever; *x = bla;
  • 我将把别名描述为当通过两种独立的方式寻址或访问存储时发生的,每个方式都在另一个的活动生命周期内。如果要以这种方式定义别名,并指定 6.5p7 仅适用于实际涉及这种形式的别名的情况,则将消除对有效类型废话以及字符类型异常的需要,并同时允许比 C99 允许的优化更多,同时允许使用更多需要 -fno-strict-aliasing 的代码。
  • 如果代码是 short *p =(short*)someObject; *p+=1; 并且不再使用 p,则不应将其视为别名,因为 p 的活动生命周期只会延长到最后一次使用,而 someObject在此期间将永远不会通过p 以外的任何方式访问。但是,如果代码要执行对someObject(without using p) before accessing those same parts with p, the accesses to someObject` 的某些访问,则会将与这些部分主动关联的左值*p 取别名。识别别名是什么和不是别名将大大简化标准。
猜你喜欢
  • 2015-03-30
  • 2014-07-26
  • 2015-10-15
  • 2017-02-25
  • 2018-12-14
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
相关资源
最近更新 更多