clang を使用してより大きなプロジェクトをコンパイルしているときに、厄介なバグに遭遇しました。
次の小さな例を考えてみましょう。
unsigned long int * * fee();
void foo( unsigned long int q )
{
unsigned long int i,j,k,e;
unsigned long int pows[7];
unsigned long int * * table;
e = 0;
for (i = 1; i <= 256; i *= q)
pows[e++] = i;
pows[e--] = i;
table = fee(); // need to set table to something unknown
// here, otherwise the compiler optimises
// parts of the loops below away
// (and no bug occurs)
for (i = 0; i < q; i++)
for (j = 0; j < e; j++)
((unsigned char*)(*table) + 5 )[i*e + j] = 0; // bug here
}
私の知る限りでは、このコードは C 標準にまったく違反していませんが、最後の行はぎこちないように見えます (実際のプロジェクトでは、このようなコードはプリプロセッサ マクロを過度に使用したために表示されます)。
これを最適化レベル -O1 以上で clang (バージョン 3.1 以上) でコンパイルすると、メモリ内の間違った位置にコードが書き込まれます。
clang/LLVM によって生成されるアセンブリ ファイルの重要な部分は次のようになります: (これは GAS 構文なので、Intel に慣れている人は注意してください!)
[...]
callq _fee
leaq 6(%rbx), %r8 ## at this point, %rbx == e-1
xorl %edx, %edx
LBB0_4:
[...]
movq %r8, %rsi
imulq %rdx, %rsi
incq %rdx
LBB0_6:
movq (%rax), %rcx ## %rax == fee()
movb $0, (%rcx,%rsi)
incq %rsi
[conditional jumps back to LBB0_6 resp. LBB0_4]
[...]
言い換えれば、命令は
(*table)[i*(e+5) + j] = 0;
上記の最後の行の代わりに。の選択+ 5
は任意で、他の整数を加算 (または減算) しても同じ動作になります。それで、これは LLVM の最適化のバグですか、それともここで未定義の動作が起こっているのでしょうか?
編集:(unsigned char*)
最後の行でキャストを省略すると、バグが消えることにも注意してください。一般に、このバグは変更に対して非常に敏感であるように見えます。