最近发现了一个奇怪的编译参数-fno-strict-aliasing,好奇之下做了一点研究;
重点参考Understanding C/C++ Strict Aliasing;
所谓的aliasing就是多个变量指向同一块内存,变量之间互为别名;
strict-aliasing是一种编译器希望开发者遵守的规则:虽然C/C++变量可以随便赋值(强制类型转换),但也请你们收敛一点,别太天马行空了;
如果开发者按照这个规则写代码了,编译器就可以做更好的代码优化,比如这个例子:
void foo(double *dblptr) { anint = 1; *dblptr = 0; bar(anint); }
如果开发者能够注意不要把int*转成double*,bar(anint)可以直接优化成bar(1);
但没有任何约束不允许这样做,因而编译器不敢做这样的优化,只能在bar(anint)将anint传入bar之前加一条汇编指令再读一下anint的值;
如果开发者确定自己的代码遵守这样的规则了,可以在编译时加一个优化参数-fstrict-aliasing,这个参数在gcc的-O2、-O3、-Os优化级别下都是默认开启的。
然后我对Understanding C/C++ Strict Aliasing文中的两个主要例子做了一下测试:
例子一:
#include <stdio.h> int anint; void bar(int a) { printf("%d ", a); } void foo(double *dblptr) { anint = 1; *dblptr = 0; bar(anint); } int main() { foo((double*)&anint); return 0; }
编译器版本 | 编译参数 | 结果 |
gcc 4.4.7 | g++ | 0 |
g++ -O3 | 1 | |
g++ -O3 -fno-strict-aliasing | 0 | |
gcc 4.8.5 | g++ | 0 |
g++ -O3 | 1 | |
g++ -O3 -fno-strict-aliasing | 0 | |
gcc 7.3.0 | g++ | 0 |
g++ -O3 | 1 | |
g++ -O3 -fno-strict-aliasing | 0 |
可以看到,这个case被gcc编译器优化坏了,可以用-fno-strict-aliasing规避;
例子二:
#include <iostream> #include <iomanip> using namespace std; typedef unsigned int uint32_t; typedef unsigned short uint16_t; uint32_t swaphalves(uint32_t a) { uint32_t acopy = a; uint16_t *ptr = (uint16_t*)&acopy;// can't use static_cast<>, not legal. // you should be warned by that. uint16_t tmp = ptr[0]; ptr[0] = ptr[1]; ptr[1] = tmp; return acopy; } int main() { uint32_t a; a = 32; cout << hex << setfill('0') << setw(8) << a << endl; a = swaphalves(a); cout << setw(8) << a << endl; }
编译器版本 | 编译参数 | 结果 |
gcc 4.4.7 | g++ | 00000020 00200000 |
g++ -O3 | 00000020 00000020 |
|
gcc 4.8.5 | g++ | 00000020 00200000 |
g++ -O3 | 00000020 00200000 |
|
gcc 7.3.0 | g++ | 00000020 00200000 |
g++ -O3 | 00000020 00200000 |
发现这个case有点意思,只在4.4版本的编译器上会出现问题,高版本编译器上已经修正了。
没有精力再深入研究,就到此为止。
最后再贴上strict aliasing的规则说明,下面这篇文章给出了较好的中文翻译,而且作者显然比我研究的更深入,我就直接抄过来了:
https://blog.csdn.net/dbzhang800/article/details/6720141
- 兼容类型(指相同类型?)或差别仅在于signed、unsigned、const、volatile的类型(比如 const unsigned long *和 long*)
- 聚合类型(struct或class)或联合类型(union)可以alias它们所包含的类型(比如 int 和 包含有int的结构体(包括间接包含))
- 字符类型(char *、signed char*、unsinged char*)可以 alias 任何类型的指针
- [C++] 基类的类型(可能带有const、volatile等cv修饰)可以alias派生类的类型