我如何进行无限循环(不会被优化掉)? (Clang,Clang)

问题描述 投票:5回答:2

C11标准似乎暗示不应优化具有恒定控制表达式的迭代语句。我从this answer中获得建议,该标准专门引用了标准草案的6.8.5节:

实现的假设其控制表达式不是常量表达式的迭代语句可以终止。

在那个答案中,它提到不应像while(1) ;这样的循环进行优化。

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

在我的机器上,这会打印出begin,然后打印出一条非法指令(在ud2之后放置一个die()陷阱)。 On 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输出正确的无内存访问无限循环?

c clang language-lawyer compiler-optimization
2个回答
1
投票

[我认为Clang在这里是错误的,GCC确实可以正常工作,只优化了unreachable打印语句,但退出了循环。当组合内联并确定其可对循环执行的操作时,Clang会如何失败。

此行为特别怪异-它删除了最终的打印内容,因此“看到”了无限循环,但随后也摆脱了循环。

据我所知,甚至更糟。删除内联,我们得到:

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"

这样就创建了函数,并优化了调用。这比预期的还要有弹性:

#include <stdio.h>

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

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

导致该函数的非最佳汇编,但是再次优化了函数调用!更糟糕的是:

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

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

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

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

完成工作。它很费力(很明显)在优化上,并留在了多余的最后printf中。至少程序不会停止。也许毕竟是海湾合作委员会?


0
投票

该循环没有副作用,因此可以进行优化。循环实际上是零工作单位的无限次迭代。在数学和逻辑上这是未定义的,并且标准没有说如果每件事情都可以在零时间内完成,那么是否允许一个实现完成无数个事情。 Clang的解释在将无穷大时间零视为零而不是无穷大时是完全合理的。该标准没有说明如果实际上所有循环工作都已完成,那么无限循环是否可以结束。

允许编译器优化标准中定义的不可观察行为。这包括执行时间。不需要保留以下事实:如果不进行优化,则循环将花费无数的时间。可以将其更改为更短的运行时间-实际上,这是大多数优化的关键。您的循环已优化。

© www.soinside.com 2019 - 2024. All rights reserved.