gpt4 book ai didi

c - 我如何制作一个不会被优化掉的无限空循环?

转载 作者:行者123 更新时间:2023-12-01 23:04:28 25 4
gpt4 key购买 nike

C11 标准似乎暗示不应优化带有常量控制表达式的迭代语句。我正在接受 this answer 的建议,其中特别引用了标准草案中的第 6.8.5 节:

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



在那个答案中它提到了像 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");
}

...叮当版画 begin其次是 unreachable仿佛无限循环从未存在过。

你如何让 Clang 在优化打开的情况下输出一个正确的、无内存访问的无限循环?

最佳答案

C11 标准是这样说的,6.8.5/6:

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)



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

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

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



在您的情况下, while(1)是一个非常清晰的常量表达式,因此实现可能不会假设它终止。这样的实现将无可救药地被破坏,因为“永远”循环是一种常见的编程结构。

然而,据我所知,循环后“无法访问的代码”会发生什么,并没有明确定义。但是,clang 的行为确实很奇怪。将机器代码与 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

叮当 9.0.0 -O3 -std=c11 -pedantic-errors
main:                                   # @main
push rax
mov edi, offset .Lstr
call puts
.Lstr:
.asciz "begin"

gcc 生成循环,clang 刚进入树林并以错误 255 退出。

我倾向于这种不合规的叮当行为。因为我试图像这样进一步扩展你的例子:
#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试图帮助编译器进一步发展。应该很清楚,这个函数将挂断,仅从那个关键字。
setjmp第一次执行时会返回 0,所以这个程序应该只是撞到 while(1)并停在那里,只打印“开始”(假设\n 刷新标准输出)。这发生在 gcc 中。

如果循环被简单地删除,它应该打印“begin”2 次,然后打印“unreachable”。然而,在 clang ( godbolt ) 上,它会打印“开始”1 次,然后在返回退出代码 0 之前“无法访问”。无论你怎么写,这都是完全错误的。

我在这里找不到任何声明未定义行为的案例,所以我认为这是 clang 中的一个错误。无论如何,这种行为使 clang 100% 对嵌入式系统等程序毫无用处,在这些程序中,您必须能够依靠挂起程序的永恒循环(在等待看门狗等时)。

关于c - 我如何制作一个不会被优化掉的无限空循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59925618/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com