有时关于 Stack overflow 的问题和答案建议将指针转换作为一种有效的类型双关方式。它经常被声称这会破坏严格的别名并因此调用未定义行为的说法所拒绝。
现代目标真的关心严格别名吗?在这种情况下,是否有任何程序实例会显示意外行为?
最佳答案
是的,严格别名是一种非常真实的现象,现代编译器经常利用它来执行优化。
考虑以下代码 -
typedef struct
{
char a;
} my_struct;
void foo( int * a, my_struct * b, int count )
{
int i;
for (i=0;i<count;i++)
{
a[i] += b->a;
}
}
当使用 clang 3.8.0-2
(这是一个非常现代的编译器)为 X64 目标(这也是一个非常现代的目标)使用命令编译时 -
clang -m64 -S -O2 -std=c11 foo.c -fno-vectorize -fno-unroll-loops
生成以下程序集(简化并采用 AT&T 语法)-
foo:
testl %edx, %edx
jle .LBB0_3
movsbl (%rsi), %eax # Value loaded only once before start of loop
.align 16, 0x90
.LBB0_2:
addl %eax, (%rdi)
addq $4, %rdi
decl %edx
jne .LBB0_2
.LBB0_3:
retq
可以看到 b->a
的值在循环开始之前只加载一次,并添加到 a
中的所有整数。
但是如果这个函数被调用为 -
my_struct a[100];
// initializaion of values in a;
...
foo((int*)a, a, 2); // Breaking strict aliasing
现在很容易看出结果与预期不符。
在使用-fno-strict-aliasing
的较低优化级别或,编译器在内部添加指令movsbl (%rsi), %eax
循环体。
因此可以公平地得出结论,具有现代编译器的现代架构确实利用了严格的别名,因此不能将指针转换用作类型双关的一种方式。
引用资料: 该示例的灵感来自于此 blog post
关于c - 是否有现代目标/编译器的任何示例,其中打破 C 中的严格别名会影响程序结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50391984/