嚴格別名規則“-fstrict-aliasing”和“-fno-strict-aliasing”及型別雙關
“-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”別名其它任何型別
相關文章
- 嚴格模式和非嚴格模式區別模式
- TypeScript型別系統基本規則TypeScript型別
- java 基本型別的轉換規則Java型別
- JS資料型別轉換規則JS資料型別
- javascript嚴格模式下的8點規則JavaScript模式
- TypeScript type 型別別名TypeScript型別
- 檔案型別和副檔名型別
- 識別符號的命名規則和規範符號
- AWS EC2 例項型別命名規則型別
- TS資料型別:型別別名/聯合型別/字面量型別/型別推論等綱要資料型別
- TypeScript 強大的型別別名TypeScript型別
- rust trait 關聯型別和泛型的區別RustAI型別泛型
- javascript原始值和引用值型別及區別JavaScript型別
- C#名稱空間、型別的別名管理C#型別
- 瞭解 Go 1.9 的型別別名Go型別
- ls命令+檔案型別+別名(alias)型別
- C語言(typedef 型別取別名)C語言型別
- 關於 Go 中 Map 型別和 Slice 型別的傳遞Go型別
- ? 圖解 == 操作符規則和不同型別間轉換規則圖解型別
- 值型別和引用型別型別
- js基本型別和引用型別區別JS型別
- joins型別名詞型別
- JAVA_資料型別介紹與基本資料型別之間的運算規則Java資料型別
- Java資料型別及型別轉換Java資料型別
- service型別及功能簡介+pod型別型別
- JavaScript值型別和引用型別JavaScript型別
- Date型別和Regex型別型別
- Swift值型別和引用型別Swift型別
- 型別預設和any型別型別
- 自定義值型別一定不要忘了重寫Equals,否則效能和空間雙雙堪憂型別
- python資料型別和四則運算Python資料型別
- 使用者識別規則
- Elasticsearch第四篇:索引別名、新增或修改對映規則Elasticsearch索引
- Python批量修改檔名和檔案型別Python型別
- 程式碼靜態掃描規則——型別轉換檢查型別
- JavaScript 運算子規則與隱式型別轉換詳解JavaScript型別
- SELinux策略語言--型別強制(編寫TE規則)Linux型別
- Java的基本型別和引用型別Java型別