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输出正确的无内存访问无限循环?
[我认为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
中。至少程序不会停止。也许毕竟是海湾合作委员会?
该循环没有副作用,因此可以进行优化。循环实际上是零工作单位的无限次迭代。在数学和逻辑上这是未定义的,并且标准没有说如果每件事情都可以在零时间内完成,那么是否允许一个实现完成无数个事情。 Clang的解释在将无穷大时间零视为零而不是无穷大时是完全合理的。该标准没有说明如果实际上所有循环工作都已完成,那么无限循环是否可以结束。
允许编译器优化标准中定义的不可观察行为。这包括执行时间。不需要保留以下事实:如果不进行优化,则循环将花费无数的时间。可以将其更改为更短的运行时间-实际上,这是大多数优化的关键。您的循环已优化。