次のコードは GCC で無限ループに入ります。
#include <iostream>
using namespace std;
int main(){
int i = 0x10000000;
int c = 0;
do{
c++;
i += i;
cout << i << endl;
}while (i > 0);
cout << c << endl;
return 0;
}
それで、取引はこうなります:符号付き整数のオーバーフローは技術的には未定義の動作です。しかし、x86 上の GCC は、オーバーフロー時にラップする x86 整数命令を使用して整数演算を実装します。
したがって、未定義の動作であるにもかかわらず、オーバーフロー時にラップすると予想していました。しかし、明らかにそうではありません。では、何を見逃したのでしょうか?
私はこれを次を使用してコンパイルしました:
~/Desktop$ g++ main.cpp -O2
GCC 出力:
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0
... (infinite loop)
最適化を無効にすると、無限ループは発生せず、出力は正しくなります。Visual Studio でもこれを正しくコンパイルし、次の結果を返します。
正しい出力:
~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3
他にもいくつかバリエーションがあります:
i *= 2; // Also fails and goes into infinite loop.
i <<= 1; // This seems okay. It does not enter infinite loop.
関連するバージョン情報はすべて次のとおりです。
~/Desktop$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ..
...
Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)
~/Desktop$
そこで質問です:これは GCC のバグでしょうか? それとも、GCC が整数演算を処理する方法について私が何かを誤解しているのでしょうか?
*このバグは C でも再現すると想定しているため、この C にもタグを付けています。(まだ検証していません。)
編集:
ループの組み立ては次のようになります: (正しく認識していれば)
.L5:
addl %ebp, %ebp
movl $_ZSt4cout, %edi
movl %ebp, %esi
.cfi_offset 3, -40
call _ZNSolsEi
movq %rax, %rbx
movq (%rax), %rax
movq -24(%rax), %rax
movq 240(%rbx,%rax), %r13
testq %r13, %r13
je .L10
cmpb $0, 56(%r13)
je .L3
movzbl 67(%r13), %eax
.L4:
movsbl %al, %esi
movq %rbx, %rdi
addl $1, %r12d
call _ZNSo3putEc
movq %rax, %rdi
call _ZNSo5flushEv
cmpl $3, %r12d
jne .L5
ベストアンサー1
標準では未定義の動作であるとされているが、それはそれを意味します何でも起こり得ます。「何でも」には、「通常は整数が折り返されますが、時々奇妙なことが起こる」ことも含まれます。
はい、x86 CPUでは整数いつもの期待通りにラッピングします。これはその例外の一つです。コンパイラは、未定義の動作が発生しないと想定し、ループ テストを最適化して取り除きます。ラップアラウンドを本当に実行したい場合は、コンパイル時-fwrapv
にg++
またはに渡しますgcc
。これにより、明確に定義された (2 の補数) オーバーフロー セマンティクスが提供されますが、パフォーマンスが低下する可能性があります。