web-dev-qa-db-ja.com

オンラインIDEでプログラムの動作がおかしい

私は以下のC++プログラムに遭遇しました( source ):

#include <iostream>
int main()
{
    for (int i = 0; i < 300; i++)
        std::cout << i << " " << i * 12345678 << std::endl;
}

それは単純なプログラムのように見え、私のローカルマシン上で正しい出力を与えます。

0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574

しかし、 codechef のようなオンラインIDEでは、次の出力が得られます。

0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597

forループが300で終了しないのはなぜですか?また、このプログラムは常に4169で終了します。他の値ではなく4169なのはなぜですか?

77
arpanmangal

オンラインコンパイラはGCCまたは互換コンパイラを使用すると仮定します。もちろん、他のコンパイラーでも同じ最適化を行うことができますが、GCCのドキュメントでは、それが何を行うのかがよく説明されています。

-faggressive-loop-optimizations

このオプションは、言語の制約を使用してループの反復回数の境界を導出するようループオプティマイザーに指示します。これは、たとえば、符号付き整数のオーバーフローまたは範囲外の配列アクセスを引き起こすことによって、ループコードが未定義の動作を呼び出さないことを前提としています。ループの反復回数の境界は、ループの展開と剥離、およびループ終了テストの最適化をガイドするために使用されます。このオプションはデフォルトで有効になっています。

このオプションは、UBが証明されている場合に基づいて仮定を行うことを許可するだけです。これらの仮定を活用するには、定数の折りたたみなど、他の最適化を有効にする必要があります。


符号付き整数オーバーフローには未定義の動作があります。オプティマイザーは、iの値が173を超えるとUBが発生することを証明できました。また、UBがないと想定できるため、iが173を超えないことも想定できます。その後、i < 300が常に真であることをさらに証明できるため、ループ条件を最適化することができます。

なぜ他の値ではなく4169なのですか?

これらのサイトはおそらく、表示する出力行(または文字やバイト)の数を制限し、偶然同じ制限を共有します。

102
eerorika

"未定義の動作は未定義です。"(c)

Codechefで使用されるコンパイラは、次のロジックを使用するようです。

  1. 未定義の動作は発生しません。
  2. i * 12345678がオーバーフローすると、i > 173の場合はUBになります(32ビットintsと仮定)。
  3. したがって、i173を超えることはできません。
  4. したがって、i < 300は不要であり、trueに置き換えることができます。

ループ自体は無限に見える。どうやら、codechefは特定の時間後にプログラムを停止するか、出力を切り捨てます。

34
HolyBlackCat

コンパイラーは、未定義の動作は発生しないと想定できます。符号付きオーバーフローはUBであるため、i * 12345678 > INT_MAX、したがってi <= INT_MAX / 12345678 < 300がなく、したがってi < 300チェックを削除しないと想定できます。

6
yassin