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

前端 未结 13 1421
后悔当初
后悔当初 2020-12-23 13:16

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

相关标签:
13条回答
  • 2020-12-23 13:29

    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.

    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 break statements 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.

    0 讨论(0)
  • 2020-12-23 13:29

    An empty while loop doesn't have any side effects on the system.

    Therefore Clang removes it. There are "better" ways to achieve the intended behavior that force you to be more obvious of your intentions.

    while(1); is baaadd.

    0 讨论(0)
  • 2020-12-23 13:36

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

    The simplest solution:

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

    Godbolt link

    0 讨论(0)
  • 2020-12-23 13:36

    The loop has no side-effects, and so can be optimized out. The loop is effectively an infinite number of iterations of zero units of work. This is undefined in mathematics and in logic and the standard doesn't say whether an implementation is permitted to complete an infinite number of things if each thing can be done in zero time. Clang's interpretation is perfectly reasonable in treating infinity times zero as zero rather than infinity. The standard doesn't say whether or not an infinite loop can end if all the work in the loops is in fact completed.

    The compiler is permitted to optimize out anything that's not observable behavior as defined in the standard. That includes execution time. It is not required to preserve the fact that the loop, if not optimized, would take an infinite amount of time. It is permitted to change that to a much shorter run time -- in fact, that the point of most optimizations. Your loop was optimized.

    Even if clang translated the code naively, you could imagine an optimizing CPU that can complete each iteration in half the time the previous iteration took. That would literally complete the infinite loop in a finite amount of time. Does such an optimizing CPU violate the standard? It seems quite absurd to say that an optimizing CPU would violate the standard if it's too good at optimizing. The same is true of a compiler.

    0 讨论(0)
  • 2020-12-23 13:39

    I'm sorry if this is absurdly not the case, I stumbled upon this post and I know because my years using Gentoo Linux distro that if you want the compiler to not optimize your code you should use -O0(Zero). I was curious about it, and compiled and ran the above code, and the loop do goes indefinitely. Compiled using clang-9:

    cc -O0 -std=c11 test.c -o test
    
    0 讨论(0)
  • 2020-12-23 13:44

    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 unreachable print 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.

    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 goto etc... At this point I would give up. If you must use 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?

    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 n1528 has these as undefined behavior if I read that right. Specifically

    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.

    0 讨论(0)
提交回复
热议问题