1. ホーム
  2. c++

[解決済み] なぜGCCでx86の整数オーバーフローは無限ループを引き起こすのですか?

2022-06-24 11:16:44

質問

以下のコードは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のタグもつけています(まだ検証していません)。

EDITです。

ループの組み立てはこんな感じです。(私が正しく認識していれば)

.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

どのように解決するのですか?

標準では未定義の動作とされている場合。 という意味です。 . あらゆることが起こり得ます。 "Anything" には "usually integers wrap around, but on occasion weird stuff happens" が含まれます。

はい、x86 CPU では、整数は は通常 は期待通りの方法でラップされます。 これはその例外の一つです。 コンパイラは未定義の動作を起こさないことを想定しており、ループテストを最適化しています。本当に折り返しが欲しいのであれば -fwrapvg++ または gcc を使用します。これにより、明確に定義された(二重補数)オーバーフローセマンティクスが得られますが、パフォーマンスを低下させる可能性があります。