Type punning と strict Aliasing Rule
Type punning
とはメモリの中身はそのままにして「別の型として解釈し直す」ことだ。以下のサンプルコードはその一例である。皆さんはこれを見て何を思うか。
int main() {
float x = 2.3;
unsigned int y = *(unsigned int*)(&x); // Type punning
std::printf("x = %d (0x%x)\n", x, y);
}
実は、上記のコードはStrict Aliasing Rule
[1]を違反していてUndefined Behavior
を引き起こしている。
unsigned int y = *(unsigned int*)(&x); // type punning, **Undefined Behavior**
Strict Aliasing Rule
というのは「異なる型(特に非互換のポインタ型)を通して同じメモリにアクセスしてはいけない」というルールで、多くの最適化手法がこのルールを前提にしているため、破るとUBになってしまうのだ。
安全に型を変えるには?
最適化によって振る舞いを変更されるリスクなく安全に上記の操作を実現する方法はただ一つしかない。それは、以下のように別のメモリ領域を確保したうえでコピーする方法だ。
#include <bit>
#include <cstring>
int main() {
float x = 2.3;
// int y = *(int*)(&x); // UB!
int y = std::bit_cast<int>(x); // Ok. C++20以降はこちらが推奨
int y_;
std::memcpy(&y_, &x, sizeof(int)); // Ok
}
コピーが増えて嫌だなと思うかもしれないが、最適化によって除去される可能性がある上、何よりUBで不安定になるよりはいい。
ただし注意点として、std::vector
などのコピー方法が固定ではない型(is_trivially_copyable<T> == false
)[2]の場合は、std::memcopy
が使えず、安全に別の型として解釈し直すことはできない。
Aliasingがある型
ただし、型の間にaliasing
がある場合は、ポインタ経由で再解釈しても安全[1:1]。
まず、バイト列(char*
,std::byte
,...)として再解釈することは常に許されている。
float x = 2.3;
char* y = reinterpret_cast<char*>(&x); // Ok
std::cout << (int)y[0] << "\n"; // Ok
また、signed
やunsigned
を付けなおすことも許されている。
int x = -1;
unsigned int* y = reinterpret_cast<unsigned int*>(&x); // Ok
std::cout << *y << "\n";
まとめ
C++のコンパイラはStrict Aliasing Rule
を守っている前提で最適化を頑張ってくれる。気が付かないうちにルールを破り、未定義動作の沼にはまってしまわないよう、けちけちせずstd::memcpy
やstd::bit_cast
を使って安全なコードを書くことを心掛けたい。
今回、このルールに関しては先輩に指摘されて知ることになった。先輩有難う。より詳しいことはこの資料が参考になる。
追伸、GCCの-Wstrict-aliasing
オプション[3]によるコンパイル時検査やclangの-fsanitize=type
オプション[4]による実行時検査によってStrict Aliasing Rule
違反を検知できる可能性がある。(詳しくはコメント欄参照)-Wall
はつけておいて損がないなぁ。
Discussion
利用可能(C++20以降)ならば、コンパイル時に型サイズ互換性検査を行う
std::bit_cast
が望ましいですね。std::memcpy
はC++20未満環境での選択肢と考えるのがベターと思います。Strict Aliasing Rule違反は、GCCであれば
-Wstrict-aliasing
(または-Wall
) オプションによるコンパイル時検査、Clangであれば-fsanitize=type
オプションによる実行時検査でも検出可能です。(VisualC++はStrict Aliasing Ruleによる積極的な最適化を行わないため本件と無縁)Demo: https://godbolt.org/z/vMPP7qzdf
情報提供ありがとうございます。
コンパイル時検査や実行時検査のサポートがあるんですね。絶対つけろと言われる
-Wall
にそんなオプションが埋まっていたとは知りませんでした。。コメントを受けて、本文を改良しました。ご指摘ありがとうございました。