嚴格別名規則“-fstrict-aliasing”和“-fno-strict-aliasing”及型別雙關

一見發表於2019-01-21

 

“-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”別名其它任何型別

 

 

相關文章