This is a Clang bug
... 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.
See @Arnavion's answer for 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.
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 call
to 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 its caller (Godbolt). -std=gnu11
vs. -std=gnu99
doesn't change anything.
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.)
If you like being explicit, asm volatile("" :::);
makes it an Extended Asm statement which explicitly doesn't have a "memory"
clobber. (Recent GCC versions changed to make a memory clobber implicit for Basic Asm statements with a non-empty template string, so this is still equivalent to asm("");
or asm("" :::);
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 also be 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.
(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 asm
statement 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.)
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.
#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:
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 main
that calls infloop() { while(1); }
to the same first puts
, but then just stops emitting instructions for main
after 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).
The valid options would be to
- emit a
label: jmp label
infinite loop
- or (if we accept that the infinite loop can be removed) emit another call to print the 2nd string, and then
return 0
from main
.
Crashing or otherwise continuing without printing "unreachable" is clearly not ok for a C11 implementation, unless there's UB that I haven't noticed.
Footnote 1:
For the record, I agree with @Lundin's answer which cites the standard for 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).
This is the set of conditions that would let a loop be compiled to an empty asm loop for 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.)
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 make about math and infinities and how long it takes to do an infinite amount of work on some hypothetical machine.
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 allowing removal 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.
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 does happen in GCC and Clang, except for Clang when the infinite loop is inside a wrapper function).
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);
.
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 hlt
instruction did exist, I don't know if it saved a meaningful amount of power until CPUs started having low-power idle states.)
Coding style note: I like while(1) {}
or while(spin_condition) {}
. The empty {}
is visually distinct so it doesn't look like a function call with an arg. A ;
on the next line is not bad and pretty eye-catching, which is maybe a good thing since infinite empty loops should be rare. while(1){
}
split across two lines seems like you just forgot to include a body.
exit()
, and because code may have discovered a situation where it cannot guarantee that the effects of continued execution would not be worse than useless. A jump-to-self loop is a pretty lousy way of handling such situations, but it may nonetheless be the best way of handling a bad situation.