のC11標準では、定数制御式を含む反復文は最適化されるべきではないと示唆されているようです。私はこの答え、ここではドラフト標準のセクション 6.8.5 を具体的に引用しています。
制御式が定数式ではない反復文は、実装によって終了すると想定される場合があります。
その回答では、次のようなループはwhile(1) ;
最適化の対象にならないと述べられています。
cc -O2 -std=c11 test.c -o test
では、なぜ Clang/LLVM は ( でコンパイルされた)以下のループを最適化するのでしょうか?
#include <stdio.h>
static void die() {
while(1)
;
}
int main() {
printf("begin\n");
die();
printf("unreachable\n");
}
私のマシンでは、次begin
のように出力されます。不正な命令でクラッシュする(ud2
の後に仕掛けられた罠die()
)。コンパイラエクスプローラー(別名 Godbolt) では、 の呼び出し後に何も生成されないことがわかりますputs
。
Clang で無限ループを出力させるのは驚くほど難しい作業でした-O2
。変数を繰り返しテストすることはできますがvolatile
、それには不要なメモリ読み取りが含まれます。次のような操作を行うと:
#include <stdio.h>
static void die() {
while(1)
;
}
int main() {
printf("begin\n");
volatile int x = 1;
if(x)
die();
printf("unreachable\n");
}
...Clang は、無限ループが存在しなかったかのように、を出力しbegin
ます。unreachable
最適化を有効にして、Clang で適切なメモリアクセスなしの無限ループを出力するにはどうすればよいでしょうか?
ベストアンサー1
C11 標準 6.8.5/6 には次のように書かれています。
制御式が定数式ではない反復文156)は、その本体、制御式、または (for 文の場合) 式 3 で、入出力操作を実行せず、揮発性オブジェクトにアクセスせず、同期またはアトミック操作を実行しない場合、実装によって終了すると想定される場合があります。157 )
2 つの脚注は規範的なものではありませんが、有用な情報を提供します。
- 省略された制御式は、定数式であるゼロ以外の定数に置き換えられます。
- これは、終了が証明できない場合でも、空のループを削除するなどのコンパイラ変換を可能にすることを目的としています。
あなたの場合、while(1)
は明確な定数式なので、ない実装によって終了すると想定されます。「永遠に」ループすることは一般的なプログラミング構造であるため、このような実装は完全に壊れてしまいます。
ただし、ループ後の「到達不可能なコード」に何が起こるかは、私の知る限り、明確に定義されていません。ただし、clang は確かに非常に奇妙な動作をします。マシン コードを gcc (x86) と比較すると、次のようになります。
9.2 より-O3 -std=c11 -pedantic-errors
.LC0:
.string "begin"
main:
sub rsp, 8
mov edi, OFFSET FLAT:.LC0
call puts
.L2:
jmp .L2
クラン 9.0.0-O3 -std=c11 -pedantic-errors
main: # @main
push rax
mov edi, offset .Lstr
call puts
.Lstr:
.asciz "begin"
gcc はループを生成し、clang は行き詰まってエラー 255 で終了します。
私はこれが clang の非準拠の動作であると考えています。なぜなら、私はあなたの例を次のようにさらに拡張しようとしたからです。
#include <stdio.h>
#include <setjmp.h>
static _Noreturn void die() {
while(1)
;
}
int main(void) {
jmp_buf buf;
_Bool first = !setjmp(buf);
printf("begin\n");
if(first)
{
die();
longjmp(buf, 1);
}
printf("unreachable\n");
}
_Noreturn
コンパイラーをさらにサポートするためにC11 を追加しました。このキーワードだけでも、この関数がハングアップすることは明らかです。
setjmp
最初の実行時に 0 を返すので、このプログラムは に突入しwhile(1)
てそこで停止し、"begin" のみを印刷するはずです (\n が stdout をフラッシュすると仮定)。これは gcc で発生します。
ループが単純に削除された場合、「begin」を2回出力してから「unreachable」を出力するはずです。ただし、clangでは(ゴッドボルト) では、終了コード 0 を返す前に、「begin」を 1 回出力し、次に「unreachable」を出力します。これは、どのように表現しても明らかに間違っています。
ここでは未定義の動作を主張する根拠が見つからないため、これは clang のバグであると私は考えています。いずれにせよ、この動作により、プログラムをハングさせる永久ループ (ウォッチドッグを待機している間など) に頼らなければならない組み込みシステムなどのプログラムでは clang は 100% 役に立たなくなります。