このループで「警告: 反復 3u は未定義の動作を呼び出します」というメッセージが表示され、4 行以上出力されるのはなぜですか? 質問する

このループで「警告: 反復 3u は未定義の動作を呼び出します」というメッセージが表示され、4 行以上出力されるのはなぜですか? 質問する

これをコンパイルすると:

#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) i2 より大きい値を持つすべての反復は未定義の動作をするため -> (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を法とする算術規則によって

おすすめ記事