【问题标题】:Strict-aliasing and pointer to union fields严格别名和指向联合字段的指针
【发布时间】:2015-08-12 14:48:00
【问题描述】:

我有一个关于严格别名规则、联合和标准的问题。假设我们有以下代码:

#include <stdio.h>

union
{
    int f1;
    short f2;
} u = {0x1};

int     * a = &u.f1;
short   * b = &u.f2;

int main()
{
    u.f1 = 1;
    *a += 1;
    u.f2 = 2;
    *b *= 2;

    printf( "%d %hd\n", *a, *b);

    return 0;
}

现在让我们看看它是如何工作的:

$ gcc-5.1.0-x86_64 t.c -O3 -Wall && ./a.out 
2 4
$ gcc-5.1.0-x86_64 t.c -O3 -Wall -fno-strict-aliasing && ./a.out 
4 4

我们可以看到,严格别名会破坏依赖关系。此外,它似乎是一个正确的代码,没有违反严格的别名规则。

  1. 事实证明,与联合字段的情况相比,位于该地址的对象与所有类型的联合成员兼容吗?
  2. 如果 1 为真,编译器应该如何处理指向联合成员的指针? 允许这样的编译器行为是标准中的问题吗?如果不是 - 为什么?
  3. 一般来说,编译器的不同行为与正确的代码在任何情况下都是不可接受的。所以它似乎也是一个编译器错误(特别是如果将地址带到联合字段将在函数内部,SA 不会破坏依赖关系)。

【问题讨论】:

  • 不,这不是正确的代码。严格的别名是标准的要求,而不是编译器。 gcc 有礼貌地为损坏的代码禁用此功能。
  • @Olaf:有些实现定义了上述代码的行为,有些则没有。仅限于支持超出标准要求的行为的编译器(即最有用的程序)的代码可能不是完全可移植的,但这并不意味着它们在任何意义上都是“损坏的”。相比之下,gcc 在许多情况下的行为(尽管不是上述情况)会被彻底破坏,除非它(1)以一种会使标准变得毫无意义的方式应用“一个程序规则”,或者(2)证明它符合需要-fno-strict-aliasing-O0

标签: c standards strict-aliasing


【解决方案1】:

C 标准规定明确允许通过联合使用别名。

但是请检查以下代码:

void func(int *a, short *b)
{
     *a = 1; 
     printf("%f\n", *b);
}

严格别名规则的意图是应该假定ab 没有别名。不过你可以打电话给func(&amp;u.f1, &amp;u.f2);

为了解决这个难题,一个常识性的解决方案是说联合必须避免严格的别名规则的“绕过许可”仅适用于按名称访问联合成员的情况。

标准没有明确说明这一点。可以说“如果成员使用...”(6.5.2.3)实际上是在指定“绕过”仅在按名称访问成员时发生,但不是 100% 清楚。

但是,很难提出任何替代的和自洽的解释。一种可能的替代解释是写 func(&amp;u.f1, &amp;u.f2) 会导致 UB,因为重叠的对象被传递给一个“知道”它不接收重叠对象的函数——有点像 restrict 违规。

如果我们将第一种解释应用于您的示例,我们会说您的 printf 中的 *a 会导致 UB,因为存储在该位置的当前对象是 short,并且 6.5.2.3 不会启动因为我们没有按名称使用联合成员。

根据您发布的结果,我猜 gcc 使用的是相同的解释。

这里之前已经讨论过,但我现在找不到线程。

【讨论】:

  • 我认为理解规则的一个主要关键是认识到标准的作者认为零需要定义一种适合系统编程目的的语言,因为任何寻求实现的人都适合这样的目的可以指定别名规则不适用(尽管奇怪的是,我认为我见过更多的编译器,其作者假装该规则不存在,而不是明确放弃将其用于优化)。太糟糕了,委员会从未定义过“类型别名障碍”,因为这会使该语言适合系统编程......
  • ...并消除了别名规则对“字符类型”例外的需要。
  • @supercat 好吧,编译器已经实现了这样的事情(-fno-strict-aliasing
  • 程序是否有任何标准方式可以表明它需要能够确保在某个点需要之前使用任何类型对内存块的所有写入在该点之后使用任何类型在该内存的所有写入之前?如果程序员有办法阻止会导致问题的 1% 的别名优化,许多不需要严格别名的程序实际上可以在启用 99% 别名优化的情况下正常工作。
【解决方案2】:

C99 技术勘误 3 在第 6.5.2.3 节中阐明了基于联合方法的类型双关语:

如果用于访问联合对象内容的成员不是 与上次用于在对象中存储值的成员相同, 值的对象表示的适当部分是 如所述,重新解释为新类型中的对象表示 在 6.2.6 中(有时称为“类型双关语”的过程)。

请参阅here 从 1042 到 1044

【讨论】:

  • 鉴于唯一可以在没有 UB 的情况下访问的工会成员类型是 charsigned charunsigned char,因此该条款的实用性相当有限。
  • @supercat 你能详细说明一下吗?
  • @JerryJeremiah:根据 N1570 6.5p7,聚合类型的对象只能使用其自身类型的左值、字符类型或 封闭 聚合来访问类型。没有规则允许使用成员类型的左值访问聚合类型的对象。标准的作者可能认为编译器编写者会认识到,给定像 int *p = unionPtr-&gt;intMember; *p = 1; 这样简单的东西,质量编译器会识别 *p 和联合之间的关联,无论标准是否需要它,但是......
  • @JerryJeremiah:虽然编译器编写者似乎能够识别大多数直接使用unionPtr-&gt;intMember 形式的表达式,但他们无法可靠地处理像unionPtr-&gt;member = expression1; memberType *p = &amp;unionPtr-&gt;member; *p = expression2; memberType result = unionPtr-&gt;member; 这样的序列,尽管我不确定@987654329 如何如果编译器不能可靠地处理这样的简单用例,@ 运算符可以有意义地产生成员类型的指针。
猜你喜欢
  • 1970-01-01
  • 2014-07-26
  • 2014-07-13
  • 2023-02-02
  • 2021-06-13
  • 1970-01-01
  • 2016-11-21
  • 1970-01-01
相关资源
最近更新 更多