1. ホーム
  2. c++

C++コンパイルバグ?

2023-11-15 12:38:26

質問

次のようなコードがあります。

#include <iostream>
#include <complex>
using namespace std;

int main() {
    complex<int> delta;
    complex<int> mc[4] = {0};

    for(int di = 0; di < 4; di++, delta = mc[di]) {
        cout << di << endl;
    }

    return 0;
}

0, 1, 2, 3"と出力して止まるかと思いきや、延々と"0, 1, 2, 3, 4, 5, ......"が出力される。

比較のようです di<4 はうまく機能せず、常に真を返しているように見えます。

もし私がただコメントアウト ,delta=mc[di] をコメントアウトすると、通常通り "0, 1, 2, 3" が表示されます。無実の代入で何が問題なのでしょうか?

私は イデオン・ドットコム g++ C++14 を -O2 オプション付きで使用しています。

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

これは未定義の動作によるもので、配列にアクセスする際に mc にアクセスしています。コンパイラによっては、未定義の動作がないことを前提に、積極的にループの最適化を行う場合があります。ロジックは次のようなものになります。

  • アクセスする mc へのアクセスは未定義の動作です。
  • 未定義の動作はないとする
  • したがって di < 4 は常に真であり、そうでなければ mc[di] は未定義の動作を呼び出すからです。

最適化をオンにした gcc で -fno-aggressive-loop-optimizations フラグを使用すると、無限ループの動作がなくなります ( ライブを見る ). 一方 最適化されたライブの例ですが、-fn-aggressive-loop-optimizations を使用しない場合 は、あなたが観察したような無限ループの挙動を示します。

A godboltのライブコードの例 には di < 4 のチェックは削除され、無条件のjmpに置き換えられました。

jmp .L6

で説明したケースとほぼ同じです。 GCC pre-4.8 は壊れた SPEC 2006 ベンチマークを破壊する . この記事へのコメントは素晴らしく、一読の価値があります。それは、clang がこの記事の中で -fsanitize=undefined を使用した場合、このケースを再現することはできませんが、gccでは -fsanitize=undefined では ( ライブを見る ). オプティマイザが未定義の振る舞いを推論するバグとして最も有名なのは、おそらく Linux カーネルの NULL ポインター チェック除去 .

これは積極的な最適化ですが、C++の標準にあるように未定義の動作であることに注意する必要があります。

この国際規格が何の要件も課さない動作

つまり、本質的に何でも可能であり、それは注釈( 強調 ):

[...]許容される未定義の行動 は、状況を完全に無視することから 予測不可能な結果をもたらす というものから、翻訳中やプログラム実行中に環境に特徴的な文書化された方法で動作する 環境特有の文書化された方法で翻訳またはプログラム実行中に動作する(診断メッセージの発行の有無にかかわらず)。 診断メッセージの発行の有無にかかわらず)、翻訳または実行を終了する(診断メッセージの発行あり)。

gcc から警告を受けるためには cout をループの外側に移動させると、次のような警告が表示されます ( ライブを見る ):

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
     for(di=0; di<4;di++,delta=mc[di]){ }
     ^

というように、OPが何が起こっているのかを理解するのに十分な情報を提供することができたはずです。このような矛盾は、未定義の動作で見られる典型的なタイプです。このような警告が未定義の動作に直面して矛盾している理由をよりよく理解するために なぜ未定義の動作に基づいて最適化するときに警告できないのですか? は良い読み物です。

注意してください。 -fno-aggressive-loop-optimizations がドキュメント化されているのは gcc 4.8 リリースノート .