これをコンパイルすると:
#include <iostream>
int main()
{
for (int i = 0; i < 4; ++i)
std::cout << i*1000000000 << std::endl;
}
gcc
次の警告が表示されます。
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
std::cout << i*1000000000 << std::endl;
^
符号付き整数のオーバーフローがあることは理解しています。
理解できないのは、i
オーバーフロー操作によって値が壊れる理由です。
私は答えを読みましたGCC を使用した x86 での整数オーバーフローによって無限ループが発生するのはなぜですか?、しかし、私はまだ明確ではありませんなぜこれは起こります - 「未定義」は「何でも起こり得る」という意味だとはわかりますが、根本的な原因は何でしょうかこの特定の行動?
オンライン:http://ideone.com/dMrRKR
コンパイラ:gcc (4.8)
ベストアンサー1
符号付き整数オーバーフロー(厳密に言えば、「符号なし整数オーバーフロー」というものは存在しない)とは、未定義の動作これは、何でも起こり得ることを意味し、C++ のルールに基づいてなぜそれが起こるのかを議論するのは意味がありません。
C++11 ドラフト N3337: §5.4: 1
式の評価中に、結果が数学的に定義されていない場合、またはその型の表現可能な値の範囲外である場合、動作は未定義です。[注: C++ の既存の実装のほとんどは、整数オーバーフローを無視します。ゼロ除算、ゼロ除数を使用した剰余の形成、およびすべての浮動小数点例外の処理はマシンによって異なり、通常はライブラリ関数によって調整可能です。—注記終了]
でコンパイルされたコードはg++ -O3
警告を発します( がなくても-Wall
)
a.cpp: In function 'int main()':
a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
std::cout << i*1000000000 << std::endl;
^
a.cpp:9:2: note: containing loop
for (int i = 0; i < 4; ++i)
^
プログラムが何を実行しているかを分析できる唯一の方法は、生成されたアセンブリ コードを読み取ることです。
完全なアセンブリリストは次のとおりです。
.file "a.cpp"
.section .text$_ZNKSt5ctypeIcE8do_widenEc,"x"
.linkonce discard
.align 2
LCOLDB0:
LHOTB0:
.align 2
.p2align 4,,15
.globl __ZNKSt5ctypeIcE8do_widenEc
.def __ZNKSt5ctypeIcE8do_widenEc; .scl 2; .type 32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
.cfi_startproc
movzbl 4(%esp), %eax
ret $4
.cfi_endproc
LFE860:
LCOLDE0:
LHOTE0:
.section .text.unlikely,"x"
LCOLDB1:
.text
LHOTB1:
.p2align 4,,15
.def ___tcf_0; .scl 3; .type 32; .endef
___tcf_0:
LFB1091:
.cfi_startproc
movl $__ZStL8__ioinit, %ecx
jmp __ZNSt8ios_base4InitD1Ev
.cfi_endproc
LFE1091:
.section .text.unlikely,"x"
LCOLDE1:
.text
LHOTE1:
.def ___main; .scl 2; .type 32; .endef
.section .text.unlikely,"x"
LCOLDB2:
.section .text.startup,"x"
LHOTB2:
.p2align 4,,15
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB1084:
.cfi_startproc
leal 4(%esp), %ecx
.cfi_def_cfa 1, 0
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
pushl %ecx
.cfi_escape 0xf,0x3,0x75,0x70,0x6
.cfi_escape 0x10,0x7,0x2,0x75,0x7c
.cfi_escape 0x10,0x6,0x2,0x75,0x78
.cfi_escape 0x10,0x3,0x2,0x75,0x74
xorl %edi, %edi
subl $24, %esp
call ___main
L4:
movl %edi, (%esp)
movl $__ZSt4cout, %ecx
call __ZNSolsEi
movl %eax, %esi
movl (%eax), %eax
subl $4, %esp
movl -12(%eax), %eax
movl 124(%esi,%eax), %ebx
testl %ebx, %ebx
je L15
cmpb $0, 28(%ebx)
je L5
movsbl 39(%ebx), %eax
L6:
movl %esi, %ecx
movl %eax, (%esp)
addl $1000000000, %edi
call __ZNSo3putEc
subl $4, %esp
movl %eax, %ecx
call __ZNSo5flushEv
jmp L4
.p2align 4,,10
L5:
movl %ebx, %ecx
call __ZNKSt5ctypeIcE13_M_widen_initEv
movl (%ebx), %eax
movl 24(%eax), %edx
movl $10, %eax
cmpl $__ZNKSt5ctypeIcE8do_widenEc, %edx
je L6
movl $10, (%esp)
movl %ebx, %ecx
call *%edx
movsbl %al, %eax
pushl %edx
jmp L6
L15:
call __ZSt16__throw_bad_castv
.cfi_endproc
LFE1084:
.section .text.unlikely,"x"
LCOLDE2:
.section .text.startup,"x"
LHOTE2:
.section .text.unlikely,"x"
LCOLDB3:
.section .text.startup,"x"
LHOTB3:
.p2align 4,,15
.def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef
__GLOBAL__sub_I_main:
LFB1092:
.cfi_startproc
subl $28, %esp
.cfi_def_cfa_offset 32
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitC1Ev
movl $___tcf_0, (%esp)
call _atexit
addl $28, %esp
.cfi_def_cfa_offset 4
ret
.cfi_endproc
LFE1092:
.section .text.unlikely,"x"
LCOLDE3:
.section .text.startup,"x"
LHOTE3:
.section .ctors,"w"
.align 4
.long __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
.ident "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0"
.def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef
.def __ZNSolsEi; .scl 2; .type 32; .endef
.def __ZNSo3putEc; .scl 2; .type 32; .endef
.def __ZNSo5flushEv; .scl 2; .type 32; .endef
.def __ZNKSt5ctypeIcE13_M_widen_initEv; .scl 2; .type 32; .endef
.def __ZSt16__throw_bad_castv; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef
.def _atexit; .scl 2; .type 32; .endef
私はアセンブリを読むのがやっとですが、それでもこの行はわかりますaddl $1000000000, %edi
。結果のコードはこんな感じです。
for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
std::cout << i << std::endl;
@TC のこのコメント:
私の推測では、次のようなものだと思います: (1)
i
2 より大きい値を持つすべての反復は未定義の動作をするため -> (2)i <= 2
最適化の目的で、次のようになると想定できます -> (3) ループ条件は常に true です -> (4) 最適化によって無限ループになります。
未定義の動作のない、OP のコードのアセンブリ コードと次のコードのアセンブリ コードを比較するというアイデアが浮かびました。
#include <iostream>
int main()
{
// changed the termination condition
for (int i = 0; i < 3; ++i)
std::cout << i*1000000000 << std::endl;
}
そして実際、正しいコードには終了条件があります。
; ...snip...
L6:
mov ecx, edi
mov DWORD PTR [esp], eax
add esi, 1000000000
call __ZNSo3putEc
sub esp, 4
mov ecx, eax
call __ZNSo5flushEv
cmp esi, -1294967296 // here it is
jne L7
lea esp, [ebp-16]
xor eax, eax
pop ecx
; ...snip...
残念ながら、これはバグのあるコードを書いた結果です。
幸いなことに、より優れた診断ツールとデバッグ ツールを利用できます。それがそれらの目的です。
すべての警告を有効にする
-Wall
は、誤検知のないすべての有用な警告を有効にする gcc オプションです。これは常に使用すべき最低限のオプションです。gccには他にも多くの警告オプションがあるただし、
-Wall
誤検知を警告する可能性があるため、有効にすることはできません。残念ながら、Visual C++ は有用な警告を出す機能に関しては遅れをとっています。少なくとも IDE ではデフォルトでいくつかの警告が有効になっています。
デバッグにはデバッグフラグを使用する
- 整数オーバーフローは
-ftrapv
オーバーフロー時にプログラムをトラップします。 - Clang コンパイラはこの点で優れています。
-fcatch-undefined-behavior
未定義の動作のインスタンスを多数キャッチします (注:"a lot of" != "all of them"
)
- 整数オーバーフローは
自分で書いたわけではない、スパゲッティのようなプログラムがあり、明日発送する必要があります。助けてください!!!!!!111oneone
gccを使用する-fwrapv
このオプションは、加算、減算、乗算の符号付き算術オーバーフローが 2 の補数表現を使用してラップアラウンドすると想定するようにコンパイラに指示します。
1 - この規則は「符号なし整数オーバーフロー」には適用されない。§3.9.1.4で次のように規定されている。
符号なし整数(符号なしと宣言されている)は、2 nを法とする算術法則に従います。ここで、n は特定のサイズの整数の値表現におけるビット数です。
そして例えば、結果は数学的に定義されます - 2nUINT_MAX + 1
を法とする算術規則によって