私は以下の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
なのはなぜですか?
オンラインコンパイラはGCCまたは互換コンパイラを使用すると仮定します。もちろん、他のコンパイラーでも同じ最適化を行うことができますが、GCCのドキュメントでは、それが何を行うのかがよく説明されています。
-faggressive-loop-optimizations
このオプションは、言語の制約を使用してループの反復回数の境界を導出するようループオプティマイザーに指示します。これは、たとえば、符号付き整数のオーバーフローまたは範囲外の配列アクセスを引き起こすことによって、ループコードが未定義の動作を呼び出さないことを前提としています。ループの反復回数の境界は、ループの展開と剥離、およびループ終了テストの最適化をガイドするために使用されます。このオプションはデフォルトで有効になっています。
このオプションは、UBが証明されている場合に基づいて仮定を行うことを許可するだけです。これらの仮定を活用するには、定数の折りたたみなど、他の最適化を有効にする必要があります。
符号付き整数オーバーフローには未定義の動作があります。オプティマイザーは、i
の値が173を超えるとUBが発生することを証明できました。また、UBがないと想定できるため、i
が173を超えないことも想定できます。その後、i < 300
が常に真であることをさらに証明できるため、ループ条件を最適化することができます。
なぜ他の値ではなく4169なのですか?
これらのサイトはおそらく、表示する出力行(または文字やバイト)の数を制限し、偶然同じ制限を共有します。
"未定義の動作は未定義です。"(c)
Codechefで使用されるコンパイラは、次のロジックを使用するようです。
i * 12345678
がオーバーフローすると、i > 173
の場合はUBになります(32ビットint
sと仮定)。i
は173
を超えることはできません。i < 300
は不要であり、true
に置き換えることができます。ループ自体は無限に見える。どうやら、codechefは特定の時間後にプログラムを停止するか、出力を切り捨てます。
コンパイラーは、未定義の動作は発生しないと想定できます。符号付きオーバーフローはUBであるため、i * 12345678 > INT_MAX
、したがってi <= INT_MAX / 12345678 < 300
がなく、したがってi < 300
チェックを削除しないと想定できます。