これはClangのバグです
...無限ループを含む関数をインライン化するとき。while(1);
mainに直接表示されたときの動作は異なります。
概要とリンクについては、@ Arnavionの回答を参照してください。この回答の残りの部分は、既知のバグはもちろんのこと、バグであることを確認する前に書かれました。
タイトルの質問に答えるには:最適化されない無限の空のループを作成するにはどうすればよいですか??-
作るdie()
マクロではなく、機能を、クラン3.9以降では、このバグを回避します。(以前のClangバージョンは、ループを保持するcall
か、無限ループのある関数の非インラインバージョンにを出力します。)print;while(1);print;
関数が呼び出し元(Godbolt)にインライン化しても安全であるように見えます。 -std=gnu11
対-std=gnu99
何も変わりません。
GNU Cのみに関心がある__asm__("");
場合は、ループ内のP__J__も機能し、それを理解するコンパイラーの周囲のコードの最適化を損なうべきではありません。GNU C Basic asmステートメントは暗黙的volatile
であるため、これは、C抽象マシンで実行するのと同じ回数「実行」する必要がある目に見える副作用としてカウントされます。(そうです、ClangはGCCマニュアルで文書化されているように、CのGNU方言を実装しています。)
一部の人々は、空の無限ループを最適化することは合法であるかもしれないと主張しました。私は同意しません1、ただしそれを受け入れたとしても、Clangがループに到達できない後のステートメントを想定することは合法ではありません。し、実行が関数の終わりから次の関数またはガベージに落ちることを許すランダムな命令としてデコードします。
(これは、Clang ++の標準に準拠します(ただし、あまり有用ではありません)。副作用のない無限ループは、C ++ではUBですが、Cで
はありません。while (1)です。Cの未定義の動作 により、コンパイラーは基本的に何でも出力できます確実にUBに遭遇する実行パス上のコードの場合。asm
ループ内のステートメントはC ++のこのUBを回避します。しかし、実際には、C ++としてコンパイルされたClangは、インラインの場合を除いて、定数式の無限空ループを削除しません。 Cとしてコンパイル)
手動でインライン化while(1);
すると、Clangによるコンパイル方法が変わります。asmに無限ループが存在します。 これは、私たちがルール弁護士のPOVに期待することです。
#include <stdio.h>
int main() {
printf("begin\n");
while(1);
//infloop_nonconst(1);
//infloop();
printf("unreachable\n");
}
Godboltコンパイラエクスプローラで、Clang 9.0 -O3 -xc
をx86-64のC()としてコンパイルします。
main: # @main
push rax # re-align the stack by 16
mov edi, offset .Lstr # non-PIE executable can use 32-bit absolute addresses
call puts
.LBB3_1: # =>This Inner Loop Header: Depth=1
jmp .LBB3_1 # infinite loop
.section .rodata
...
.Lstr:
.asciz "begin"
同じコンパイラを同じオプションで使用すると、最初に同じmain
を呼び出すa がコンパイルされますが、その後は命令の発行が停止されます。したがって、先に述べたように、実行は関数の最後から次の関数に落ちます(ただし、スタックは関数のエントリに対して誤って配置されているため、有効な末尾呼び出しではありません)。infloop() { while(1); }
puts
main
有効なオプションは
label: jmp label
無限ループを放出する
- またはその後第2文字列を印刷するために別のコールを発し、そして(我々は、無限ループを除去することができることを受け入れる場合)
return 0
からmain
。
クラッシュしたり、「到達不能」を表示せずに続行したりすることは、C11の実装では、気付かないUBがない限り、明らかに問題です。
脚注1:
記録として、私は、 C11が定数式無限ループが空である(I / O、揮発性、同期、またはその他がない)場合でも、終了の仮定を許可しないという証拠の基準を引用する@Lundinの回答に同意します目に見える副作用)。
これは、通常のCPUのループを空のasmループにコンパイルできるようにする一連の条件です。(ソースで本文が空でなくても、ループの実行中は、変数への割り当てを他のスレッドやシグナルハンドラーがデータレースUBなしで見ることができません。したがって、必要に応じて、適合実装がそのようなループ本体を削除できます。次に、ループ自体を削除できるかどうかの問題が残ります。ISOC11は明示的に「いいえ」と言っています。)
C11がそのケースを、実装がループの終了を想定できない場合(およびそれがUBではない場合)として特定した場合、ループが実行時に存在することを意図していることは明らかです。有限時間で無限の量の作業を実行できない実行モデルを持つCPUをターゲットとする実装には、空の定数無限ループを削除する正当な理由がありません。または一般的にさえ、正確な表現は、それらが「終了すると見なされる」かどうかに関するものです。ループが終了できない場合は、数学と無限大についてどのような引数を指定しても、仮想マシンで無限の処理を実行するのにかかる時間に関係なく、後のコードに到達できないことを意味します。
さらに、Clangは単なるISO C準拠のDeathStation 9000ではなく、カーネルや組み込みのものを含む実際の低レベルシステムプログラミングに役立つことを目的としています。 したがって、C11 がの削除を許可することについての引数を受け入れるかどうかにかかわらずwhile(1);
、Clangが実際にそれを実行することを望んでも意味がありません。と書けばwhile(1);
、それはおそらく偶然ではなかったでしょう。(ランタイム変数制御式を使用して)偶然に無限になってしまうループを削除すると便利な場合があり、コンパイラーがそれを行うことは理にかなっています。
次の割り込みまでスピンすることはめったにありませんが、Cでそれを書き込んだ場合、それは間違いなく予想されることです。(GCCとClangで何が起こるか、無限ループがラッパー関数内にある場合のClangを除く)。
たとえば、プリミティブOSカーネルでは、スケジューラに実行するタスクがない場合、アイドルタスクが実行される可能性があります。その最初の実装はかもしれませんwhile(1);
。
または、省電力アイドル機能のないハードウェアの場合は、それが唯一の実装である可能性があります。(2000年代前半までは、x86でそれはまれではないと思いました。hlt
命令は存在しましたが、IDKがCPUが低電力のアイドル状態になるまで有意な量の電力を節約した場合。)