严格别名和指向联合字段的指针
Strict-aliasing and pointer to union fields
我有一个关于严格别名规则、联合和标准的问题。假设我们有以下代码:
#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 为真,编译器应该如何处理指向联合成员的指针? 允许这样的编译器行为是标准中的问题吗?如果不是 - 为什么?
- 一般来说,在任何情况下都不允许使用正确代码的编译器的不同行为。所以它似乎也是一个编译器错误(特别是如果将地址取到联合字段将在函数内部,SA 不会破坏依赖性)。
C 标准规定明确允许通过联合使用别名。
但是请检查以下代码:
void func(int *a, short *b)
{
*a = 1;
printf("%f\n", *b);
}
严格别名规则的目的是 a
和 b
应该被假定为不别名。但是你可以调用 func(&u.f1, &u.f2);
.
为了解决这个难题,一个常识性的解决方案是说 'bypass permit' union 必须避免严格的别名规则仅适用于按名称访问 union 成员的情况。
标准没有明确说明这一点。可以说 "If the member used..." (6.5.2.3) 实际上是在指定 'bypass' 仅在按名称访问成员时出现,但这不是 100% 清楚。
然而,很难提出任何替代的和自洽的解释。一种可能的替代解释是写 func(&u.f1, &u.f2)
导致 UB,因为重叠对象被传递给一个函数 'knows' 它不接收重叠对象——有点像 restrict
违规。
如果我们将第一个解释应用于您的示例,我们会说您的 printf
中的 *a
导致 UB,因为存储在该位置的当前对象是 short
,并且 6.5.2.3 没有启动,因为我们没有按名称使用联合成员。
根据您发布的结果,我猜测 gcc 使用的是相同的解释。
之前在这里讨论过,但我现在找不到主题。
C99 技术勘误表 3 在第 6.5.2.3 节中阐明了基于联合方法的类型双关:
If the member used to access the contents of a union object is not
the same as the member last used to store a value in the object, the
appropriate part of the object representation of the value is
reinterpreted as an object representation in the new type as described
in 6.2.6 (a process sometimes called "type punning").
参见 here 从 1042 到 1044
我有一个关于严格别名规则、联合和标准的问题。假设我们有以下代码:
#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 为真,编译器应该如何处理指向联合成员的指针? 允许这样的编译器行为是标准中的问题吗?如果不是 - 为什么?
- 一般来说,在任何情况下都不允许使用正确代码的编译器的不同行为。所以它似乎也是一个编译器错误(特别是如果将地址取到联合字段将在函数内部,SA 不会破坏依赖性)。
C 标准规定明确允许通过联合使用别名。
但是请检查以下代码:
void func(int *a, short *b)
{
*a = 1;
printf("%f\n", *b);
}
严格别名规则的目的是 a
和 b
应该被假定为不别名。但是你可以调用 func(&u.f1, &u.f2);
.
为了解决这个难题,一个常识性的解决方案是说 'bypass permit' union 必须避免严格的别名规则仅适用于按名称访问 union 成员的情况。
标准没有明确说明这一点。可以说 "If the member used..." (6.5.2.3) 实际上是在指定 'bypass' 仅在按名称访问成员时出现,但这不是 100% 清楚。
然而,很难提出任何替代的和自洽的解释。一种可能的替代解释是写 func(&u.f1, &u.f2)
导致 UB,因为重叠对象被传递给一个函数 'knows' 它不接收重叠对象——有点像 restrict
违规。
如果我们将第一个解释应用于您的示例,我们会说您的 printf
中的 *a
导致 UB,因为存储在该位置的当前对象是 short
,并且 6.5.2.3 没有启动,因为我们没有按名称使用联合成员。
根据您发布的结果,我猜测 gcc 使用的是相同的解释。
之前在这里讨论过,但我现在找不到主题。
C99 技术勘误表 3 在第 6.5.2.3 节中阐明了基于联合方法的类型双关:
If the member used to access the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning").
参见 here 从 1042 到 1044