gpt4 book ai didi

c - 为什么 gcc 不删除对非 volatile 变量的检查?

转载 作者:太空狗 更新时间:2023-10-29 16:46:54 25 4
gpt4 key购买 nike

这个问题主要是学术性的。我问是出于好奇,而不是因为这给我带来了实际问题。

考虑以下不正确的 C 程序。

#include <signal.h>
#include <stdio.h>

static int running = 1;

void handler(int u) {
running = 0;
}

int main() {
signal(SIGTERM, handler);
while (running)
;
printf("Bye!\n");
return 0;
}

这个程序是不正确的,因为处理程序中断了程序流,所以 running 可以随时修改,因此应该声明为 volatile。但是假设程序员忘记了这一点。

gcc 4.3.3,带有 -O3 标志,将循环体(在对 running 标志进行一次初始检查后)编译为无限循环

.L7:
jmp .L7

这是意料之中的。

现在我们在 while 循环中放一些小东西,比如:

    while (running)
putchar('.');

突然之间,gcc 不再优化循环条件了!循环体的程序集现在看起来像这样(同样在 -O3):

.L7:
movq stdout(%rip), %rsi
movl $46, %edi
call _IO_putc
movl running(%rip), %eax
testl %eax, %eax
jne .L7

我们看到 running 每次循环都会从内存中重新加载;它甚至没有缓存在寄存器中。显然 gcc 现在认为 running 的值可能已经改变。

那么在这种情况下,为什么 gcc 突然决定需要重新检查 running 的值?

最佳答案

在一般情况下,编译器很难确切知道函数可能访问哪些对象,因此可能会修改哪些对象。在 putchar() 的位置被调用,GCC 不知道是否可能有 putchar()可能能够修改 running 的实现所以它必须有点悲观并假设running可能实际上已经改变了。

例如,可能有一个 putchar()稍后在翻译单元中实现:

int putchar( int c)
{
running = c;
return c;
}

即使没有 putchar()在翻译单元中实现,可能会有一些东西,例如,传递 running 的地址这样的对象 putchar也许可以修改它:

void foo(void)
{
set_putchar_status_location( &running);
}

请注意您的 handler()函数是全局可访问的,所以 putchar()可能会调用 handler()本身(直接或以其他方式),这是上述情况的一个实例。

<罢工>另一方面,由于 running仅对翻译单元可见(即 static ),当编译器到达文件末尾时,它应该能够确定没有机会 putchar()访问它(假设是这种情况),并且编译器可以返回并“修复” while 循环中的悲观化。

running是静态的,编译器可能能够确定它不能从翻译单元外部访问并进行您正在谈论的优化。但是,由于可以通过 handler() 访问它和 handler()可以从外部访问,编译器无法优化访问。即使你做 handler()静态的,它可以从外部访问,因为您将它的地址传递给另一个函数。

请注意,在您的第一个示例中,即使我在上一段中提到的内容仍然正确,编译器也可以优化对 running 的访问。因为 C 语言所基于的“抽象机器模型”不考虑异步事件,除非在非常有限的情况下(其中一个是 volatile 关键字,另一个是信号处理,尽管信号处理的要求是在您的第一个示例中足够强大以防止编译器能够优化对 running 的访问)。

事实上,C99 在几乎这些确切的情况下对抽象机器行为说了一些话:

5.1.2.3/8 "Program execution"

EXAMPLE 1:

An implementation might define a one-to-one correspondence between abstract and actual semantics: at every sequence point, the values of the actual objects would agree with those specified by the abstract semantics. The keyword volatile would then be redundant.

Alternatively, an implementation might perform various optimizations within each translation unit, such that the actual semantics would agree with the abstract semantics only when making function calls across translation unit boundaries. In such an implementation, at the time of each function entry and function return where the calling function and the called function are in different translation units, the values of all externally linked objects and of all objects accessible via pointers therein would agree with the abstract semantics. Furthermore, at the time of each such function entry the values of the parameters of the called function and of all objects accessible via pointers therein would agree with the abstract semantics. In this type of implementation, objects referred to by interrupt service routines activated by the signal function would require explicit specification of volatile storage, as well as other implementation defined restrictions.

最后,你应该注意到 C99 标准还说:

7.14.1.1/5 "The signal function`

If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t...

所以严格来说running变量可能需要声明为:

volatile sig_atomic_t running = 1;

关于c - 为什么 gcc 不删除对非 volatile 变量的检查?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2518430/

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