37

コンパイラを最適化すると、データを変更しない無限ループを削除できます。

while(1) 
  /* noop */;

データフローグラフコンパイラの分析から、そのようなループは副作用のない「デッドコード」であることがわかります。

無限ループの削除はC90/C99標準で禁止されていますか?

C90またはC99標準では、コンパイラがそのようなループを削除することを許可していますか?

Upd:「MicrosoftCバージョン6.0は本質的にこの最適化を行いました。」、cafによるリンクを参照してください。

label: goto label;
return 0;

に変換されます

return 0;
4

6 に答える 6

34

C11は、この質問に対する答えを明確にしています。ドラフトC11標準セクションで、6.8.5 反復ステートメントに次の段落が追加されています。

制御式が定数式ではない反復ステートメント156)は、入出力操作を実行せず、揮発性オブジェクトにアクセスせず、本体で同期またはアトミック操作を実行せず、式を制御します。ステートメント)その式-3は、実装によって終了すると見なされる場合があります。157)

脚注157は次のように述べています。

これは、終了が証明できない場合でも、空のループの削除などのコンパイラ変換を可能にすることを目的としています。

だからあなたの特定の例:

while(1) 
  /* noop */;

制御式は定数式であるため、最適化には公平なゲームではありません。

UBとしての無限ループ

では、なぜコンパイラが上記の例外を除いて無限ループを最適化できるのでしょうか。ハンスベームは、無限ループを未定義の動作にする理由を次のように提供しています。N1528:なぜ無限ループの未定義の動作なのか。、次の引用は、関連する問題について良い感じを与えます:

N1509が正しく指摘しているように、現在のドラフトは基本的に6.8.5p6の無限ループに未定義の動作を与えます。そうすることの主な問題は、コードが潜在的に終了しないループを移動できるようにすることです。たとえば、次のループがあるとします。ここで、countとcount2はグローバル変数であり(またはそれらのアドレスが取得されています)、pはローカル変数であり、そのアドレスは取得されていません。

for (p = q; p != 0; p = p -> next) {
    ++count;
}
for (p = q; p != 0; p = p -> next) {
    ++count2;
}

これらの2つのループをマージして、次のループに置き換えることはできますか?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

無限ループに対する6.8.5p6の特別なディスペンスがないと、これは許可されません。qが循環リストを指しているために最初のループが終了しない場合、元のループはcount2に書き込みません。したがって、count2にアクセスまたは更新する別のスレッドと並行して実行できます。これは、無限ループにもかかわらずcount2にアクセスする変換バージョンでは安全ではなくなりました。したがって、変換によってデータの競合が発生する可能性があります。

このような場合、コンパイラがループの終了を証明できる可能性はほとんどありません。qが非循環リストを指していることを理解する必要があります。これは、ほとんどの主流コンパイラの能力を超えており、プログラム全体の情報がないと不可能なことがよくあります。

C99

C99にはこれが刻まれていないため、基本的にコンパイラーはプログラムの観察可能な動作をエミュレートするだけでよいというセクションで説明されているas-ifルールに5.1.2.3注目します。要件は、次のとおりです。

準拠する実装の最小要件は次のとおりです。

  • シーケンスポイントでは、以前のアクセスが完了し、後続のアクセスがまだ発生していないという意味で、揮発性オブジェクトは安定しています。
  • プログラムの終了時に、ファイルに書き込まれるすべてのデータは、抽象セマンティクスに従ってプログラムを実行した場合の結果と同じでなければなりません。
  • 対話型デバイスの入力および出力のダイナミクスは、7.19.3で指定されているように実行されるものとします。これらの要件の目的は、プログラムが入力を待機する前にプロンプ​​トメッセージが実際に表示されるように、バッファなしまたは行バッファの出力ができるだけ早く表示されるようにすることです。

これを厳密に読み取ると、実装で無限ループを最適化できるようになります。無限ループを最適化すると、観察可能な動作に変化が生じるシナリオを確実に思いつくことができます。

while(1) ;
printf( "hello world\n" ) ;

多くの人が、プロセスの終了に影響を与えることも観察可能な振る舞いであると主張するでしょう。この立場は、Cコンパイラがフェルマーの最終定理を反証することで採用されています。

コンパイラには、Cプログラムの実装方法にかなりの自由が与えられますが、その出力は、標準で説明されている「C抽象マシン」によって解釈された場合とプログラムが外部から見える動作と同じである必要があります。多くの知識のある人(私を含む)は、プログラムの終了動作を変更してはならないと言っているとこれを読んでいます。明らかに、一部のコンパイラ作成者は同意しないか、そうでなければそれが重要であるとは信じていません。合理的な人々が解釈に同意しないという事実は、C標準に欠陥があることを示しているように思われます。

アップデート

私はどういうわけか、上記の記事のフォローアップ、コンパイラと終了の再検討を見逃しました。これは、セクションについて次のように述べています5.1.2.3

2番目の要件はトリッキーなものです。抽象マシンで実行されているプログラムの終了について話している場合、プログラムが終了しないため、空虚に満たされます。コンパイラによって生成された実際のプログラムの終了について話している場合、ファイルに書き込まれるデータ(stdoutはファイル)が抽象マシンによって書き込まれるデータと異なるため、Cの実装にはバグがあります。(この読みはハンス・ベームによるものです。私はこの微妙な点を標準から外すことができませんでした。)

空のループを削除できるようにするためにC11でカーブを作成する必要があるということは、これが以前は許容できる最適化ではなかったことを意味するという、より弱い議論をすることもできます。

これは無限のgotoループにも当てはまりますか?

これは、無限のgotoループにも当てはまるという意図があると思います。C ++では、セクション1.10 [intro.multithread]に次のように記載されているため、これは明らかに当てはまります。

実装では、任意のスレッドが最終的に次のいずれかを実行すると想定する場合があります

  • 終了、
  • ライブラリI/O関数を呼び出します。
  • 揮発性オブジェクトにアクセスまたは変更する、または
  • 同期操作またはアトミック操作を実行します。

そして、で表現されている意図N1528は、CとC++の標準が一致することです。

コンパイラのバックエンドは通常、CコンパイラとC ++コンパイラの間で共有されるため、WG14とWG21が採用されるソリューションに同意することが最も重要であるように思われます。別の方法は、バックエンドによる2つの言語の特別な処理、またはCコードの処理時に最適化を無効にすることです。どちらもまったく望ましくないようです。

そして最後に言う:

WG21は、扱いを一貫させるための改善された表現を検討しています。うまくいけば、WG14は結果として生じる変更を追跡します。

5.1.2.4 現在、C11標準には、マルチスレッド実行とデータ競合のセクションに同様の表現が含まれていませんが、コンパイラN1528が無限のgotoループをCおよびC++では未定義の動作として扱うと想定するのが賢明です。

こちらのUSコメント38と、この変更が適用された論文であるN3196も参照してください。

于 2015-02-23T18:44:21.537 に答える
9

無限ループを普遍的に検出する方法はありません。停止性問題を参照してください。したがって、コンパイラが実行できる最善の方法は、適切な推測を行うことです。たとえば、OPで言及されている明らかなケースです。

しかし、なぜこれが望ましいのでしょうか?警告を発し、それでも動作を許可しているのを見ることができましたが、ループを削除することは「最適化」ではなく、プログラムの動作を変更します。

于 2010-02-01T16:19:05.857 に答える
8

ループはデッドコードではありません。基本的に、プログラムがその後に続くものに到達するのを防ぎます。これは、ループが削除された場合に発生することではないため、コンパイラーはループを削除できません。

プラットフォームに依存するアイドル命令に置き換えて、スレッドがこれ以上何もしないことをプロセッサに通知する場合があります。

コンパイラーが実行できることは、ループの後に来るコードを削除することです。これは、到達不能であり、実行されないためです。

于 2010-02-01T16:18:20.473 に答える
4

これは、私が知る限り、コンセンサスの結果なしに、これまで何度もcomp.lang.c(たとえばここで)議論されてきました。

于 2010-02-02T00:17:49.850 に答える
2

デーモンを作成するときに必要です。なぜそれらをデッドコードと呼びたいのですか?

于 2010-02-01T16:14:07.757 に答える
0

新しい標準では、コードの一部が揮発性変数にアクセスしない場合、I / Oなどを実行することが明示的に規定されていると思います。最初の部分で計算されたものに依存しない他のコードは、その前に任意にシーケンスできます。無限ループがI/Oを実行せず、プログラムの後半で使用される値を計算しない場合、コンパイラーは、他のすべてが完了するまでループの実行を延期することがあります。

于 2010-12-08T05:30:40.080 に答える