“-fstrict-aliasing”表示啟用嚴格別名規則,“-fno-strict-aliasing”表示禁用嚴格別名規則,當gcc的編譯優化參數為“-O2”、“-O3”和“-Os”時,默認會打開“-fstrict-aliasing”。
什么是嚴格別名規則?gcc對嚴格別名的定義:
In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same. 即,編譯器假定相同的內存地址絕不會存放不同類型的數據,否則即破壞了嚴格別名規則。 |
別名的定義可理解為:同一內存地址有不同的名稱,比如:
int m = 0x20190101; int* p1 = &m; int *p2 = &m; int *p3 = p2; int n = m; |
這里“&m”、“p1”、“p2”和“p3”均是同一內存地址的別名,但n不是,因此涉及嚴格別名,是和指針相關的。
下列代碼,如果使用“-O2”、“-O3”或“-Os”編譯,並且加不“-fno-strict-aliasing”,則“*s”的結果是未定義的,不同的編譯器可能產生不同的結果,即使同一編譯器也可能運行時結果不盡相同:
#include <stdio.h> int main() { int m = 0x12345678; short* s = (short*)&m; // 使用C++的方式也不可:short* s = reinterpret_cast<short*>(&m); printf("%x\n", *s); return 0; } |
gcc-4.1.2上運行情況,可以看到每次結果都不相同:
> g++ --version > g++ -g -o e e.cpp -O2 > ./e 6590 > ./e 590 > ./e ffffb590 |
怎么解決嚴格別名問題?采用類型相關(type-punning),手段是采用聯合體union,比如下面這種類型相關的用法是安全的:
#include <stdio.h> union X { int m; short s; }; int main() { X x; x.m = 0x12345678; short s = x.s; printf("%x\n", s); return 0; } |
然而,下列用法仍然是不安全的(多版本gcc實測正常,也未有“dereferencing type-punned pointer will break strict-aliasing rules”編譯告警,但gcc手冊指出結果可能不符合預期):
#include <stdio.h> union X { int m; short s; }; int main() { X x; x.m = 0x12345678; short* s = &x.s; printf("%x\n", *s); return 0; } |
下列代碼的結果也是未定義的(多版本gcc實測也正常,同樣未有編譯告警,但gcc手冊指出結果是未定義的):
#include <stdio.h> union X { int m; short s; }; int main() { int m = 0x12345678; short s = ((X*)&m)->s; printf("%x\n", s); return 0; } |
如果擔心風險,可加上編譯參數“-fno-strict-aliasing”,但這會阻止gcc相關的優化。不過下列別名總是安全的:
1) “unsigned int”別名“int”,其它類似
2) “char”別名其它任何類型