C语言 我如何制作一个不会被优化掉的无限空循环?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/59925618/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 10:48:21  来源:igfitidea点击:

How do I make an infinite empty loop that won't be optimized away?

cclanglanguage-lawyercompiler-optimization

提问by nneonneo

The C11 standard appears to imply that iteration statements with constant controlling expressions should not be optimized out. I'm taking my advice from this answer, which specifically quotes section 6.8.5 from the draft standard:

C11 标准似乎暗示不应优化带有常量控制表达式的迭代语句。我从这个答案中得到了我的建议,它特别引用了标准草案中的第 6.8.5 节:

An iteration statement whose controlling expression is not a constant expression ... may be assumed by the implementation to terminate.

其控制表达式不是常量表达式的迭代语句......可能会被实现假定为终止。

In that answer it mentions that a loop like while(1) ;should not be subject to optimization.

在该答案中,它提到while(1) ;不应进行优化之类的循环。

So...why does Clang/LLVM optimize out the loop below (compiled with cc -O2 -std=c11 test.c -o test)?

那么...为什么 Clang/LLVM 优化了下面的循环(用 编译cc -O2 -std=c11 test.c -o test)?

#include <stdio.h>

static void die() {
    while(1)
        ;
}

int main() {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

On my machine, this prints out begin, then crashes on an illegal instruction(a ud2trap placed after die()). On godbolt, we can see that nothing is generated after the call to puts.

在我的机器上,这会打印出begin,然后在非法指令ud2放置在 之后的陷阱die()上崩溃在 Godbolt 上,我们可以看到调用puts.

It's been a surprisingly difficult task to get Clang to output an infinite loop under -O2- while I could repeatedly test a volatilevariable, that involves a memory read that I don't want. And if I do something like this:

让 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 prints beginfollowed by unreachableas if the infinite loop never existed.

... Clang 打印begin后跟unreachable好像无限循环从未存在过一样。

How do you get Clang to output a proper, no-memory-access infinite loop with optimizations turned on?

你如何让 Clang 在优化打开的情况下输出一个正确的、无内存访问的无限循环?

采纳答案by Lundin

The C11 standard says this, 6.8.5/6:

C11 标准是这样说的,6.8.5/6:

An iteration statement whose controlling expression is not a constant expression,156)that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157)

控制表达式不是常量表达式的迭代语句,156)不执行输入/输出操作,不访问易失性对象,并且在其主体、控制表达式或(在 for语句)它的表达式 3,可以被实现假设终止。157)

The two foot notes are not normative but provide useful information:

这两个脚注不是规范性的,但提供了有用的信息:

156) An omitted controlling expression is replaced by a nonzero constant, which is a constant expression.

157) This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven.

156) 一个省略的控制表达式被一个非零常量替换,这是一个常量表达式。

157)这是为了允许编译器转换,例如即使在无法证明终止时也可以删除空循环。

In your case, while(1)is a crystal clear constant expression, so it may notbe assumed by the implementation to terminate. Such an implementation would be hopelessly broken, since "for-ever" loops is a common programming construct.

在您的情况下,while(1)是一个非常清晰的常量表达式,因此实现可能不会假设它终止。这样的实现将无可救药地被破坏,因为“永远”循环是一种常见的编程结构。

What happens to the "unreachable code" after the loop is however, as far as I know, not well-defined. However, clang does indeed behave very strange. Comparing the machine code with gcc (x86):

然而,据我所知,循环后“无法访问的代码”会发生什么,并没有明确定义。但是,clang 的行为确实很奇怪。将机器代码与 gcc (x86) 进行比较:

gcc 9.2 -O3 -std=c11 -pedantic-errors

海湾合作委员会 9.2 -O3 -std=c11 -pedantic-errors

.LC0:
        .string "begin"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
.L2:
        jmp     .L2

clang 9.0.0 -O3 -std=c11 -pedantic-errors

叮当 9.0.0 -O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.Lstr:
        .asciz  "begin"

gcc generates the loop, clang just runs into the woods and exits with error 255.

gcc 生成循环,clang 刚进入树林并以错误 255 退出。

I'm leaning towards this being non-compliant behavior of clang. Because I tried to expand your example further like this:

我倾向于这种不合规的叮当行为。因为我试图像这样进一步扩展你的例子:

#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");
}

I added C11 _Noreturnin an attempt to help the compiler further along. It should be clear that this function will hang up, from that keyword alone.

我添加了 C11_Noreturn试图帮助编译器进一步发展。应该很清楚,这个函数将挂断,仅从那个关键字。

setjmpwill return 0 upon first execution, so this program should just smash into the while(1)and stop there, only printing "begin" (assuming \n flushes stdout). This happens with gcc.

setjmp将在第一次执行时返回 0,所以这个程序应该只是撞到while(1)那里并停在那里,只打印“开始”(假设 \n 刷新标准输出)。这发生在 gcc 中。

If the loop was simply removed, it should print "begin" 2 times then print "unreachable". On clang however (godbolt), it prints "begin" 1 time and then "unreachable" before returning exit code 0. That's just plain wrong no matter how you put it.

如果循环被简单地删除,它应该打印“begin”2 次,然后打印“unreachable”。然而,在clang(godbolt)上,它会在返回退出代码0之前打印“begin”1次然后“unreachable”。无论你怎么写,这都是完全错误的。

I can find no case for claiming undefined behavior here, so my take is that this is a bug in clang. At any rate, this behavior makes clang 100% useless for programs like embedded systems, where you simply must be able to rely on eternal loops hanging the program (while waiting for a watchdog etc).

我在这里找不到任何声明未定义行为的案例,所以我认为这是 clang 中的一个错误。无论如何,这种行为使 clang 100% 对嵌入式系统等程序毫无用处,在这些程序中,您必须能够依靠挂起程序的永恒循环(在等待看门狗等时)。

回答by P__J__

You need to insert an expression that may cause a side-effect.

您需要插入一个可能导致副作用的表达式。

The simplest solution:

最简单的解决方案:

static void die() {
    while(1)
       __asm("");
}

Godbolt link

Godbolt 链接

回答by Arnavion

Other answers already covered ways to make Clang emit the infinite loop, with inline assembly language or other side effects. I just want to confirm that this is indeed a compiler bug. Specifically, it's a long-standing LLVM bug- it applies the C++ concept of "all loops without side-effects must terminate" to languages where it shouldn't, such as C.

其他答案已经涵盖了使用内联汇编语言或其他副作用使 Clang 发出无限循环的方法。我只想确认这确实是一个编译器错误。具体来说,这是一个长期存在的 LLVM 错误——它将 C++ 概念“没有副作用的所有循环必须终止”应用于不应该终止的语言,例如 C。

For example, the Rust programming languagealso allows infinite loops and uses LLVM as a backend, and it has this same issue.

例如,Rust 编程语言也允许无限循环并使用 LLVM 作为后端,它也有同样的问题。

In the short term, it appears that LLVM will continue to assume that "all loops without side-effects must terminate". For any language that allows infinite loops, LLVM expects the front-end to insert llvm.sideeffectopcodes into such loops. This is what Rust is planning to do, so Clang (when compiling C code) will probably have to do that too.

从短期来看,LLVM 似乎将继续假设“所有没有副作用的循环都必须终止”。对于任何允许无限循环的语言,LLVM 都希望前端将llvm.sideeffect操作码插入到此类循环中。这是 Rust 计划做的,所以 Clang(在编译 C 代码时)可能也必须这样做。

回答by Peter Cordes

This is a Clang bug

这是一个 Clang 错误

... when inlining a function containing an infinite loop. The behaviour is different when while(1);appears directly in main, which smells very buggy to me.

... 内联包含无限循环的函数时。当while(1);直接出现在 main 中时,行为是不同的,这对我来说很臭。

See @Arnavion's answerfor a summary and links. The rest of this answer was written before I had confirmation that it was a bug, let alone a known bug.

有关摘要和链接,请参阅@Arnavion 的回答。这个答案的其余部分是在我确认这是一个错误之前写的,更不用说一个已知的错误了。



To answer the title question: How do I make an infinite empty loop that won't be optimized away?? -
make die()a macro, not a function, to work around this bug in Clang 3.9 and later. (Earlier Clang versions either keeps the loop or emits a callto a non-inline version of the function with the infinite loop.) That appears to be safe even if the print;while(1);print;function inlines into itscaller (Godbolt). -std=gnu11vs. -std=gnu99doesn't change anything.

回答标题问题:如何制作一个不会被优化掉的无限空循环?? -在 Clang 3.9 及更高版本中
创建die()一个宏,而不是一个函数来解决这个错误。(早期的 Clang 版本要么保持循环,要么call使用无限循环向函数的非内联版本发出 a。)即使print;while(1);print;函数内联到调用者(Godbolt)中,这似乎也是安全的。 -std=gnu11vs.-std=gnu99不会改变任何东西。

If you only care about GNU C, P__J__'s __asm__("");inside the loop also works, and shouldn't hurt optimization of any surrounding code for any compilers that understand it. GNU C Basic asm statements are implicitly volatile, so this counts as a visible side-effect that has to "execute" as many times as it would in the C abstract machine. (And yes, Clang implements the GNU dialect of C, as documented by the GCC manual.)

如果您只关心 GNU C,循环内的P__J____asm__("");也可以工作,并且不应该损害任何理解它的编译器对任何周围代码的优化。GNU C Basic asm 语句是隐式的volatile,所以这算作一个可见的副作用,它必须像在 C 抽象机器中那样多次“执行”。(是的,Clang 实现了 C 的 GNU 方言,如 GCC 手册所述。)



Some people have argued that it might be legal to optimize away an empty infinite loop. I don't agree1, but even if we accept that, it can't alsobe legal for Clang to assume statements after the loop are unreachable,and let execution fall off the end of the function into the next function, or into garbage that decodes as random instructions.

有些人认为优化掉一个空的无限循环可能是合法的。我不同意1,但即使我们接受这一点,就不能成为合法的Clang的承担循环之后的语句是不可达,并让执行脱落功能的结束到下一个功能,或者进入垃圾解码为随机指令。

(That would be standards-compliant for Clang++ (but still not very useful); infinite loops without any side effects are UB in C++, but not C.
Is while(1); undefined behavior in C?UB lets the compiler emit basically anything for code on a path of execution that will definitely encounter UB. An asmstatement in the loop would avoid this UB for C++. But in practice, Clang compiling as C++ doesn't remove constant-expression infinite empty loops except when inlining, same as when compiling as C.)

(这将符合 Clang++ 的标准(但仍然不是很有用);没有任何副作用的无限循环是 C++ 中的 UB,但不是 C。
是 while(1);C 中的未定义行为?UB 使编译器基本上可以发出任何内容对于肯定会遇到 UB 的执行路径上的代码。asm循环中的语句将避免 C++ 的 UB。但实际上,Clang 编译为 C++ 不会删除常量表达式无限空循环,除非内联时,与 when 相同编译为 C。)



Manually inlining while(1);changes how Clang compiles it: infinite loop present in asm.This is what we'd expect from a rules-lawyer POV.

手动内联会while(1);改变 Clang 的编译方式:asm 中存在无限循环。这是我们对规则律师 POV 的期望。

#include <stdio.h>
int main() {
    printf("begin\n");
    while(1);
    //infloop_nonconst(1);
    //infloop();
    printf("unreachable\n");
}

On the Godbolt compiler explorer, Clang 9.0 -O3 compiling as C (-xc) for x86-64:

在 Godbolt 编译器资源管理器上,Clang 9.0 -O3 编译为 C ( -xc) for x86-64:

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"

The same compiler with the same options compiles a mainthat calls infloop() { while(1); }to the same first puts, but then just stops emitting instructions for mainafter that point. So as I said, execution just falls off the end of the function, into whatever function is next (but with the stack misaligned for function entry so it's not even a valid tailcall).

具有相同选项的同一个编译器编译 amain调用infloop() { while(1); }相同的 first puts,但在那之后停止发出指令main。所以正如我所说,执行只是从函数的末尾开始,进入下一个函数(但堆栈未对齐以用于函数入口,因此它甚至不是有效的尾调用)。

The valid options would be to

有效的选择是

  • emit a label: jmp labelinfinite loop
  • or (if we accept that the infinite loop can be removed) emit another call to print the 2nd string, and then return 0from main.
  • 发出label: jmp label无限循环
  • 或者(如果我们接受可以删除无限循环)发出另一个调用来打印第二个字符串,然后return 0main.

Crashing or otherwise continuing without printing "unreachable" is clearly not ok for a C11 implementation, unless there's UB that I haven't noticed.

崩溃或以其他方式继续而不打印“无法访问”显然不适用于 C11 实现,除非有我没有注意到的 UB。



Footnote 1:

脚注 1:

For the record, I agree with @Lundin's answer which cites the standardfor evidence that C11 doesn't allow assumption of termination for constant-expression infinite loops, even when they're empty (no I/O, volatile, synchronization, or other visible side-effects).

作为记录,我同意@Lundin 的回答,该回答引用了证据标准,即 C11 不允许假设常量表达式无限循环终止,即使它们为空(无 I/O、易失性、同步或其他可见的副作用)。

This is the set of conditions that would let a loop be compiled to an empty asm loopfor a normal CPU. (Even if the body wasn't empty in the source, assignments to variables can't be visible to other threads or signal handlers without data-race UB while the loop is running. So a conforming implementation could remove such loop bodies if it wanted to. Then that leaves the question of whether the loop itself can be removed. ISO C11 explicitly says no.)

这是一组条件,可以将循环编译为普通 CPU的空 asm 循环。(即使源中的主体不是空的,在循环运行时,没有数据竞争 UB 的其他线程或信号处理程序也无法看到对变量的赋值。因此,如果需要,符合要求的实现可以删除此类循环主体到。然后就留下了是否可以删除循环本身的问题。ISO C11 明确说不。)

Given that C11 singles out that case as one where the implementation can't assume the loop terminates (and that it's not UB), it seems clear they intend the loop to be present at run-time. An implementation that targets CPUs with an execution model that can't do an infinite amount of work in finite time has no justification for removing an empty constant infinite loop. Or even in general, the exact wording is about whether they can be "assumed to terminate" or not. If a loop can't terminate, that means later code is not reachable, no matter what arguments you makeabout math and infinities and how long it takes to do an infinite amount of work on some hypothetical machine.

鉴于 C11 将这种情况作为一种实现不能假设循环终止(并且它不是 UB)的情况,很明显他们打算在运行时出现循环。以 CPU 为目标的实现,其执行模型不能在有限时间内完成无限量的工作,没有理由删除空的常量无限循环。甚至一般来说,确切的措辞是关于它们是否可以“假设终止”。如果循环无法终止,则意味着无法访问后面的代码,无论对数学和无穷大提出什么论点,以及在某个假设的机器上执行无限量的工作需要多长时间。

Further to that, Clang isn't merely an ISO C compliant DeathStation 9000, it's intended to be useful for real-world low-level systems programming, including kernels and embedded stuff.So whether or not you accept arguments about C11 allowingremoval of while(1);, it doesn't make sense that Clang would want to actually do that. If you write while(1);, that probably wasn't an accident. Removal of loops that end up infinite by accident (with runtime variable control expressions) can be useful, and it makes sense for compilers to do that.

此外,Clang 不仅仅是符合 ISO C 的 DeathStation 9000,它还旨在用于现实世界的低级系统编程,包括内核和嵌入式内容。因此,无论您是否接受有关 C11允许删除 的论点while(1);,Clang 想要真正这样做是没有意义的。如果你写while(1);,那可能不是意外。删除意外结束的循环(使用运行时变量控制表达式)可能很有用,编译器这样做是有意义的。

It's rare that you want to just spin until the next interrupt, but if you write that in C that's definitely what you expect to happen. (And what doeshappen in GCC and Clang, except for Clang when the infinite loop is inside a wrapper function).

您很少想一直旋转直到下一个中​​断,但是如果您用 C 编写它,那绝对是您期望发生的事情。(以及在 GCC 和 Clang会发生什么,当无限循环在包装函数内时 Clang 除外)。

For example, in a primitive OS kernel, when the scheduler has no tasks to run it might run the idle task. A first implementation of that might be while(1);.

例如,在原始 OS 内核中,当调度程序没有要运行的任务时,它可能会运行空闲任务。第一个实现可能是while(1);.

Or for hardware without any power-saving idle feature, that might be the only implementation. (Until the early 2000s, that was I think not rare on x86. Although the hltinstruction did exist, IDK if it saved a meaningful amount of power until CPUs started having low-power idle states.)

或者对于没有任何省电空闲功能的硬件,这可能是唯一的实现。(直到 2000 年代初,我认为这在 x86 上并不少见。虽然该hlt指令确实存在,但 IDK 如果在 CPU 开始处于低功耗空闲状态之前节省了大量电量。)

回答by jonathanjo

Just for the record, Clang also misbehaves with goto:

只是为了记录,Clang 也行为不端goto

static void die() {
nasty:
    goto nasty;
}

int main() {
    int x; printf("begin\n");
    die();
    printf("unreachable\n");
}

It produces the same output as in the question, i.e.:

它产生与问题相同的输出,即:

main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

I see don't see any way to read this as permitted in C11, which only says:

我看不到 C11 中允许的任何方式来阅读它,它只说:

6.8.6.1(2) A gotostatement causes an unconditional jump to the statement prefixed by the named label in the enclosing function.

6.8.6.1(2)goto语句导致无条件跳转到封闭函数中以命名标签为前缀的语句。

As gotois not an "iteration statement" (6.8.5 lists while, doand for) nothing about the special "termination-assumed" indulgences apply, however you want to read them.

由于goto不是“迭代声明”(6.8.5 列出whiledofor),没有任何关于特殊“假设终止”的放纵适用,但是您想阅读它们。

Per original question's Godbolt link compiler is x86-64 Clang 9.0.0 and flags are -g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0 -fcolor-diagnostics -fno-crash-diagnostics -O2 -std=c11 example.c

每个原始问题的 Godbolt 链接编译器是 x86-64 Clang 9.0.0,标志是 -g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0 -fcolor-diagnostics -fno-crash-diagnostics -O2 -std=c11 example.c

With others such as x86-64 GCC 9.2 you get the pretty well perfect:

使用 x86-64 GCC 9.2 等其他版本,您将获得非常完美的效果:

.LC0:
  .string "begin"
main:
  sub rsp, 8
  mov edi, OFFSET FLAT:.LC0
  call puts
.L2:
  jmp .L2

Flags: -g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c

标志: -g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c

回答by PSkocik

I'll play the devil's advocate and argue that the standard does not explicitly forbid a compiler from optimizing out an infinite loop.

我将扮演魔鬼的拥护者并争辩说标准没有明确禁止编译器优化无限循环。

An iteration statement whose controlling expression is not a constant expression,156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157)

控制表达式不是常量表达式的迭代语句,156)不执行输入/输出操作,不访问易失性对象,并且在其主体、控制表达式或(在 for语句)它的表达式 3,可以被实现假定为终止。157)

Let's parse this. An iteration statement that satisfies certain criteria may be assumed to terminate:

我们来解析一下。可以假设满足某些标准的迭代语句终止:

if (satisfiesCriteriaForTerminatingEh(a_loop)) 
    if (whatever_reason_or_just_because_you_feel_like_it)
         assumeTerminates(a_loop);

This doesn't say anything about what happens if the criteria aren't satisfied and assuming that a loop may terminate even then isn't explicitly forbidden as long as other rules of the standard are observed.

这并没有说明如果不满足标准会发生什么,并且假设循环可能会终止,只要遵守标准的其他规则,即使这样也不会明确禁止。

do { } while(0)or while(0){}are after all iteration statements (loops) that don't satisfy the criteria that allow a compiler to just assume on a whim that they terminate and yet they obviously do terminate.

do { } while(0)或者while(0){}毕竟是不满足条件的迭代语句(循环),这些条件允许编译器随心所欲地假设它们终止,但它们显然确实终止了。

But can the compiler just optimize while(1){}out?

但是编译器可以优化while(1){}吗?

5.1.2.3p4says:

5.1.2.3p4说:

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

在抽象机中,所有表达式都按照语义指定的方式进行评估。如果一个实际的实现可以推断出它的值未被使用并且没有产生所需的副作用(包括由调用函数或访问易失性对象引起的任何副作用),则它不需要计算表达式的一部分。

This mentions expressions, not statements, so it's not 100% convincing, but it certainly allows calls like:

这提到了表达式,而不是语句,所以它不是 100% 令人信服的,但它肯定允许这样的调用:

void loop(void){ loop(); }

int main()
{
    loop();
}

to be skipped. Interestingly, clang does skip it, and gcc doesn't.

被跳过。有趣的是,clang 确实跳过了它,而 gcc 没有

回答by kabanus

I have been convinced this is just a plain old bug. I leave the my tests below and in particular the reference to the discussion in the standard committee for some reasoning I previously had.

我一直确信这只是一个普通的老错误。我把我的测试留在下面,特别是参考标准委员会的讨论,因为我以前有过一些推理。



I think this is undefined behavior (see end), and Clang just has one implementation. GCC indeed works as you expect, optimizing out only the unreachableprint statement but leaving the loop. Some how Clang is oddly making decisions when combining the in-lining and determining what it can do with the loop.

我认为这是未定义的行为(见结尾),而 Clang 只有一个实现。GCC 确实按您的预期工作,仅优化了unreachable打印语句但离开了循环。在结合内联并确定它可以用循环做什么时,Clang 是如何奇怪地做出决定的。

The behavior is extra weird - it removes the final print, so "seeing" the infinite loop, but then getting rid of the loop as well.

这种行为非常奇怪 - 它删除了最终打印,因此“看到”了无限循环,但随后也摆脱了循环。

It's even worse as far as I can tell. Removing the inline we get:

据我所知,情况更糟。删除我们得到的内联:

die: # @die
.LBB0_1: # =>This Inner Loop Header: Depth=1
  jmp .LBB0_1
main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

so the function is created, and the call optimized out. This is even more resilient than expected:

所以函数被创建,调用被优化了。这甚至比预期的更有弹性:

#include <stdio.h>

void die(int x) {
    while(x);
}

int main() {
    printf("begin\n");
    die(1);
    printf("unreachable\n");
}

results in a very non-optimal assembly for the function, but the function call is again optimized out! Even worse:

导致该函数的程序集非常非最佳,但函数调用再次被优化了!甚至更糟:

void die(x) {
    while(x++);
}

int main() {
    printf("begin\n");
    die(1);
    printf("unreachable\n");
}

I made a bunch of other test with adding a local variable and increasing it, passing a pointer, using a gotoetc... At this point I would give up. If you must use clang

我做了一堆其他测试,添加一个局部变量并增加它,传递一个指针,使用一个goto等等......在这一点上我会放弃。如果你必须使用clang

static void die() {
    int volatile x = 1;
    while(x);
}

does the job. It sucks at optimizing (obviously), and leaves in the redundant final printf. At least the program does not halt. Maybe GCC after all?

做这项工作。它在优化方面很糟糕(显然),并留下了多余的 final printf。至少程序不会停止。也许 GCC 毕竟?

Addendum

附录

Following discussion with David, I yield that the standard does not say "if the condition is constant, you may not assume the loop terminates". As such, and granted under the standard there is no observable behavior (as defined in the standard), I would argue only for consistency - if a compiler is optimizing out a loop because it assume it terminates, it should not optimize out following statements.

在与大卫讨论之后,我发现标准没有说“如果条件不变,你可能不会假设循环终止”。因此,根据标准,没有可观察到的行为(如标准中所定义),我只会争论一致性 - 如果编译器正在优化循环,因为它假设它终止,它不应该优化以下语句。

Heck n1528has these as undefined behavior if I read that right. Specifically

哎呀n1528有这些未定义行为,如果我没有看错。具体来说

A major issue for doing so is that it allows code to move across a potentially non-terminating loop

这样做的一个主要问题是它允许代码在一个潜在的非终止循环中移动

From here I think it can only devolve into a discussion of what we want(expected?) rather than what is allowed.

从这里开始,我认为它只能讨论我们想要什么(预期?)而不是允许什么。

回答by H.S.

It seems that this is a bug in the Clang compiler. If there isn't any compulsion on the die()function to be a static function, do away with staticand make it inline:

似乎这是 Clang 编译器中的一个错误。如果该die()函数没有任何强制成为静态函数,请取消static并使其成为inline

#include <stdio.h>

inline void die(void) {
    while(1)
        ;
}

int main(void) {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

It's working as expected when compiled with the Clang compiler and is portable as well.

它在使用 Clang 编译器编译时按预期工作,并且也是可移植的。

Compiler Explorer (godbolt.org)- clang 9.0.0 -O3 -std=c11 -pedantic-errors

编译器资源管理器 (godbolt.org)- clang 9.0.0-O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB0_1
.Lstr:
        .asciz  "begin"

回答by bta

The following appears to work for me:

以下似乎对我有用:

#include <stdio.h>

__attribute__ ((optnone))
static void die(void) {
    while (1) ;
}

int main(void) {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

at godbolt

上帝之箭

Explicitly telling Clang not to optimize that one function causes an infinite loop to be emitted as expected. Hopefully there's a way to selectively disable particular optimizations instead of just turning them all off like that. Clang still refuses to emit code for the second printf, though. To force it to do that, I had to further modify the code inside mainto:

明确告诉 Clang 不要优化该函数会导致按预期发出无限循环。希望有一种方法可以有选择地禁用特定优化,而不是像那样将它们全部关闭。不过,Clang 仍然拒绝为第二个发出代码printf。为了强迫它这样做,我不得不进一步修改里面的代码main

volatile int x = 0;
if (x == 0)
    die();

It looks like you'll need to disable optimizations for your infinite loop function, then ensure that your infinite loop is called conditionally. In the real world, the latter is almost always the case anyway.

看起来您需要禁用对无限循环函数的优化,然后确保有条件地调用无限循环。在现实世界中,无论如何几乎总是后者。

回答by supercat

A conforming implementation may, and many practical ones do, impose arbitrary limits on how long a program may execute or how many instructions it would execute, and behave in arbitrary fashion if those limits are violated or--under the "as-if" rule--if it determines that they will inevitably be violated. Provided that an implementation can successfully process at least one program that nominally exercises all the limits listed in N1570 5.2.4.1 without hitting any translation limits, the existence of limits, the extent to which they are documented, and the effects of exceeding them, are all Quality of Implementation issues outside the jurisdiction of the Standard.

一个符合标准的实现可能,而且许多实际的实现,对程序可以执行的时间或它将执行的指令数量施加任意限制,并且如果违反这些限制或 - 在“as-if”规则下以任意方式运行--如果它确定它们将不可避免地受到侵犯。如果实现可以成功处理至少一个名义上执行 N1570 5.2.4.1 中列出的所有限制而不会达到任何转换限制的程序,限制的存在,它们被记录的程度,以及超过它们的影响,标准管辖范围之外的所有实施质量问题。

I think the intention of the Standard is quite clear that compilers shouldn't assume that a while(1) {}loop with no side-effects nor breakstatements will terminate. Contrary to what some people might think, the authors of the Standard were not inviting compiler writers to be stupid or obtuse. A conforming implementation might usefully to decide to terminate any program which would, if not interrupted, execute more side-effect free instructions than there are atoms in the universe, but a quality implementation shouldn't perform such action on the basis of any assumption about termination but rather on the basis that doing so could be useful, and wouldn't (unlike clang's behavior) be worse than useless.

我认为标准的意图非常明确,编译器不应该假设while(1) {}没有副作用或break语句的循环会终止。与某些人可能认为的相反,标准的作者并没有邀请编译器作者变得愚蠢或迟钝。一个符合要求的实现可能有助于决定终止任何程序,如果不被中断,将执行比宇宙中的原子更多的无副作用指令,但质量实现不应该基于任何假设来执行这样的操作终止,而是基于这样做可能有用,并且不会(与 clang 的行为不同)比无用更糟糕。