如何制作不会被优化的无限空循环? [英] How do I make an infinite empty loop that won't be optimized away?

查看:364
本文介绍了如何制作不会被优化的无限空循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C11标准似乎暗示不应优化具有恒定控制表达式的迭代语句.我正在从此答案中获取建议,该答案特别引用了标准草案中的6.8.5节:

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:

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

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

在该答案中,它提到不应对while(1) ;这样的循环进行优化.

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

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

So...why does Clang/LLVM optimize out the loop below (compiled with 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");
}

在我的机器上,这会打印出begin,然后在一条非法指令上崩溃(在die()之后的ud2陷阱). 在Godbolt 上,我们看到在调用puts后没有任何生成.

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

要让Clang在-O2下输出无限循环是一件非常困难的任务-同时我可以反复测试volatile变量,该变量涉及不需要的内存读取.如果我做这样的事情:

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

#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 prints begin followed by unreachable as if the infinite loop never existed.

如何在启用优化的情况下让Clang输出正确的无内存访问无限循环?

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

推荐答案

C11标准是6.8.5/6:

The C11 standard says this, 6.8.5/6:

其控制表达式不是常量表达式的迭代语句, 156) 不执行任何输入/输出操作,不访问易失性对象,并且不执行任何操作 它的主体中的同步或原子操作,控制表达式或(对于for语句而言)其expression-3可以由以下实现实现: 157)

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)

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

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

156)省略的控制表达式将由非零常量代替,这是一个常量表达式.

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

157)这旨在允许编译器进行转换,例如删除空循环,即使在发生以下情况时也是如此: 无法证明终止.

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

在您的情况下,while(1)是一个清晰的常量表达式,因此实现可能会假定终止它.由于永远"的循环是一种常见的编程构造,因此这种实现将无可救药地被破坏.

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

据我所知,循环后无法访问的代码"发生了什么,尚不清楚.但是,c确实确实表现得很奇怪.比较机器代码和gcc(x86):

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):

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

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

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

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

gcc生成了循环,叮当声刚好进入树林,并以错误255退出.

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

我倾向于这种不兼容的clang行为.因为我试图像这样进一步扩展您的示例:

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

我添加了C11 _Noreturn,以尝试进一步帮助编译器.应该清楚的是,仅从该关键字开始,该函数就会挂断.

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

setjmp将在第一次执行时返回0,因此该程序应粉碎到while(1)并在此处停止,仅打印"begin"(假定\ n刷新stdout).这发生在gcc中.

setjmp will 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.

如果仅删除了循环,则应打印"begin"两次,然后打印"unreachable".但是,在clang( godbolt )上,它先打印"begin"(开始)一次,然后打印"unreachable"(不可达),然后返回退出代码0.不管您怎么说,这都是很明显的错误.

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.

我在这里找不到主张未定义行为的理由,所以我认为这是clang中的错误.无论如何,这种行为会使clang 100%对于嵌入式系统之类的程序毫无用处,在嵌入式程序中,您仅必须能够依赖永久性的循环挂起程序(在等待看门狗等时).

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).

这篇关于如何制作不会被优化的无限空循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆