GCC和Clang的两个值得了解的编译器开关,在系统编程中非常有用。
-fno-strict-aliasing
禁止依赖于严格别名规则的优化。严格别名规则指的是两个不相关类型的指针解引用不会相互影响,一个例外是(signed/unsigned) char指针解引用会与任何其它指针解引用互相影响。
遵守严格别名规则的话,只能使用memcpy或char*互拷进行数据重解析,有些代码实现起来性能肯定会大打折扣。比如如果开启严格别名规则优化的话,memcpy高效C/C++语言inline实现只能转换为char*然后期待编译器给你进行循环展开,如果转换为int*企图手动实现循环展开,可能会被优化掉导致程序跑飞。
-fwrapv
将有符号数溢出定义为2的补码回卷,禁止依赖于有符号数溢出是未定义行为的优化。假定有符号数溢出是未定义行为的优化会优化掉手工检测溢出的代码,因此如果需要手工检测溢出,是需要关闭相关优化的。
这两个开关很多软件都在用,Linux内核在使用-fno-strict-aliasing,很多编程语言和数据库等系统软件也在使用-fwrapv。
GCC和Clang在-O2、-O3、-Os、-Ofast优化级别默认会开启相关优化,所以需要使用这两个开关关掉这些优化。
# 不开优化
gcc -o prog prog.c
# 开-O2优化,不相关类型指针解引用、有符号数溢出可能被优化掉
gcc -O2 -o prog prog.c
# 开-O2优化,不优化不相关类型指针解引用、有符号数溢出
gcc -O2 -fno-strict-aliasing -fwrapv -o prog proc.c
【参考资料】
下面的文章介绍了Linus对于-fno-strict-aliasing和-fwrapv的口水战:
https://zine.la/article/686f5e1e554a4f439bef689005771519/
Linus 又开怼:有时候标准就是一坨屎! - OSCHINA - 中文开源技术交流社区
下面的文章介绍了Clang是如何看待和处理未定义行为的:
每个C程序员应该知道的未定义行为_x86 ud2_GnakIewiy的博客-CSDN博客
https://www.cnblogs.com/foohack/p/3582239.html
【严格别名规则的示例】
#include <stdio.h>
void check1(short *h, long *k)
{
*h = 5;
*k = 6; // 编译器认为不会改变*h
if (*h == 5)
printf("strict aliasing problem 1\n");
}
void check2(short *h, char *k)
{
*h = 5;
*k = 6; // 编译器认为可能改变*h
if (*h == 5)
printf("strict aliasing problem 2\n");
}
void check3(short *h, char *k)
{
*h = 5;
*(int*)k = 6; // 编译器仍然认为不会改变*h
if (*h == 5)
printf("strict aliasing problem 3\n");
}
void check4(char *h, short *k)
{
*h = 5;
*k = 6; // 编译器认为可能改变*h
if (*h == 5)
printf("strict aliasing problem 4\n");
}
int main()
{
long k[1] = { 0 };
check1((short *)k, (long *)k);
check2((short *)k, (char *)k);
check3((short *)k, (char *)k);
check4((char *)k, (short *)k);
}
【有符号数溢出的示例】
#include <stdio.h>
void testoverflow(int a)
{
if (a + 1 < a) // 编译器认为总是false
printf("overflow\n");
}