Jul*_*ius 5 c strict-aliasing language-lawyer c11
我目前想知道严格别名规则背后的基本原理.我知道C中不允许使用某些别名,并且目的是允许优化,但我很惊讶这是在定义标准时跟踪类型转换的首选解决方案.
所以,显然下面的例子违反了严格的别名规则:
uint64_t swap(uint64_t val)
{
uint64_t copy = val;
uint32_t *ptr = (uint32_t*)© // strict aliasing violation
uint32_t tmp = ptr[0];
ptr[0] = ptr[1];
ptr[1] = tmp;
return copy;
}
Run Code Online (Sandbox Code Playgroud)
我可能是错的,但据我所知,编译器应该完全和平凡地能够跟踪类型转换并避免对任何事物上显式转换的类型进行优化(就像它避免对同类型指针进行优化)用受影响的值调用.
那么,严格的别名规则存在哪些问题我错过了编译器无法轻易解决自动检测可能的优化问题)?
Eri*_*hil 12
因为,在该示例中,编译器可以看到所有代码,所以编译器可以假设地确定所请求的内容并生成期望的汇编代码.然而,证明在理论上不需要严格别名规则的一种情况的证明并没有证明没有其他情况需要它.
考虑代码是否包含:
foo(&val, ptr)
Run Code Online (Sandbox Code Playgroud)
这里的声明foo是void foo(uint64_t *a, uint32_t *b);.然后,在内部foo,可能在另一个翻译单元中,编译器将无法知道a并b指向同一对象的(部分).
这时有两种选择:一,语言可允许混淆,在这种情况下,编译器,而转换foo,无法进行优化依靠事实*a和*b是不同的.例如,无论何时写入内容*b,编译器都必须生成汇编代码以重新加载*a,因为它可能已更改.*a不允许进行优化,例如在使用寄存器时保留寄存器的副本.
第二个选择,两个,是禁止别名(具体来说,如果程序执行,则不定义行为).在这种情况下,编译器可以进行优化依靠事实*a和*b是不同的.
C委员会选择了第二选项,因为它提供了更好的性能,同时不会过度限制程序员.
它允许编译器优化变量重载,而无需限制指针限定.
例:
int f(long *L, short *S)
{
*L=42;
*S=43;
return *L;
}
int g(long *restrict L, short *restrict S)
{
*L=42;
*S=43;
return *L;
}
Run Code Online (Sandbox Code Playgroud)
gcc -O3 -fno-strict-aliasing在x86_64上编译:
f:
movl $43, %eax
movq $42, (%rdi)
movw %ax, (%rsi)
movq (%rdi), %rax ; <<*L reloaded here cuz *S =43 might have changed it
ret
g:
movl $43, %eax
movq $42, (%rdi)
movw %ax, (%rsi)
movl $42, %eax ; <<42 constant-propagated from *L=42 because *S=43 cannot have changed it (because of `restrict`)
ret
Run Code Online (Sandbox Code Playgroud)
在x86_64上编译gcc -O3(隐含-fstrict-alising):
f:
movl $43, %eax
movq $42, (%rdi)
movw %ax, (%rsi)
movl $42, %eax ; <<same as w/ restrict
ret
g:
movl $43, %eax
movq $42, (%rdi)
movw %ax, (%rsi)
movl $42, %eax
ret
Run Code Online (Sandbox Code Playgroud)
https://gcc.godbolt.org/z/rQDNGt
当您使用大型数组时,这可能会有很大帮助,否则可能导致大量不必要的重新加载.