未定義動作
言語仕様として定義されていない動作。
未定義とはプログラマの鼻から悪魔を飛び出させても良いという意味である。
コンパイラは未定義動作を発見すると好きに解釈する権利を与えられ、勝手な最適化をかけまくった挙げ句大変な結果になってしまう。
とくに未定義動作発見時のコンパイラの暴れ方が凄まじいのがC++言語である。
たとえば、C++では副作用の無い無限ループは未定義である。
code:loop.cpp
while(true) {}
//以下も同値
int func() { return func(); }
副作用のない無限再帰を起こす関数funcを記述したloop.cppをclangで最適化をかけてコンパイルすると以下のような結果になる。 code:loop.o
loop.o: ファイル形式 elf64-x86-64
セクション .text の逆アセンブル:
0000000000000000 <_Z4funcv>:
0: c3 retq
訳の分からない適当な値を返すだけの関数になってしまっていることが分かる。
funcを呼び出した値を出力するプログラムを書いてみる。
code:test.cpp
int func(){return func();}
int main(){
printf("%d\n", func());
}
このコードは次のように最適化される。
code:optimize
0000000000001150 <main>:
1150: 50 push %rax
1151: 48 8d 3d ac 0e 00 00 lea 0xeac(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
1158: 31 c0 xor %eax,%eax
115a: e8 d1 fe ff ff callq 1030 <printf@plt>
115f: 31 c0 xor %eax,%eax
1161: 59 pop %rcx
1162: c3 retq
1163: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
116a: 00 00 00
116d: 0f 1f 00 nopl (%rax)
関数呼び出しがどこにもなくなっているのが分かるだろう。これを実際に実行すると訳の分からない値が出力される。
code:result
./a.out
525231704
./a.out
-1170545704
./a.out
-1389581976