- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
这个问题主要是学术性的。我问是出于好奇,而不是因为这给我带来了实际问题。
考虑以下不正确的 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
orraise
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 asvolatile sig_atomic_t
...
所以严格来说running
变量可能需要声明为:
volatile sig_atomic_t running = 1;
关于c - 为什么 gcc 不删除对非 volatile 变量的检查?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2518430/
这将是一篇很长的文章,为了将其上下文化并提供尽可能多的信息,我必须仔细浏览各种链接和引号——这通常是我们进入 C/C++ 标准兔子洞的唯一方法。如果您对这篇文章有更好的引用或任何其他改进,请告诉我。但
我想知道 volatile 关键字与 register、const 和 static 结合的不同用途关键词。我不确定有什么影响,所以我认为: register volatile int T=10; 建
让我们考虑以下 Java 代码 int x = 0; int who = 1 Thread #1: (1) x++; (2) who = 2; Thread #2 while(who
有一个函数“remove_cv”(http://en.cppreference.com/w/cpp/types/remove_cv)可以删除常量和 volatile 。 我的问题是为什么可以从“con
我正在尝试在下面的“MpscQueue.h”中的嵌入式目标上实现多个生产者(通过中断)、单个消费者(通过应用程序线程)队列。 我想知道我是否可以安全地删除一些 volatile下面的用法(见内联问
我的问题适用于最初为 null 的字段,然后初始化为非 null 值,然后不再更改。 由于该字段需要尽快可供所有线程使用,因此我需要使用 volatile 。 但是,如果我想尽可能避免 volatil
我以前见过几次类似 fld = fld 的东西,但在所有这些情况下,可以消除虚拟写入并获得更好的性能。 public class Tst{ public volatile int fld =
看完this question和 this (尤其是第二个答案)我对 volatile 及其与内存屏障有关的语义感到非常困惑。 在上面的例子中,我们写入了一个 volatile 变量,这会导致一个 m
如下所示,该程序有一个共享 var flag,但不带 volatile : public class T { public static void main(String[] args) {
我明白声明 int *volatile ptr; 表示指针本身是volatile int a=10; int *volatile ptr=&a; 现在 ptr 和 a 都在更新。会不会导致访问ptr时
最近我需要比较两个 uint 数组(一个是 volatile 数组,另一个是非 volatile 数组),结果令人困惑,我一定是对 volatile 数组有一些误解。 我需要从输入设备读取一个数组并将
这两个 C 定义有什么区别? volatile uint32_t *ptr1 = (volatile uint32_t *)0x20040000; volatile uint32_t *ptr1 =
// structure is like this, but not exact formation. class queue { volatile List worksWaiting; }
考虑以下这段代码: struct S{ int i; S(int); S(const volatile S&); }; struct S_bad{ int i; }; vola
在 Windows x64 上,考虑到一些额外的见解,何时允许编译器将 ABI 标记为 volatile 的寄存器视为非 volatile 寄存器?我有一个反汇编函数,其中 r11 用于在函数调用后恢
我对下面的代码段有疑问。结果可能有 [0, 1, 0] 的结果(这是用 JCStress 执行的测试)。那么这怎么会发生呢?我认为应该在写入 Actor2 (guard2 = 1) 中的 guard2
好吧,假设我有一堆变量,其中一个声明为 volatile: int a; int b; int c; volatile int v; 如果一个线程写入所有四个变量(最后写入 v),而另一个线程读取所有
我试图理解为什么这个例子是一个正确同步的程序: a - volatile Thread1: x=a Thread2: a=5 因为存在冲突访问(存在对 a 的写入和读取),所以在每个顺序一致性执行中,
我正在编写一个需要同时支持 volatile 和非 volatile 实例的类( volatile 实例使用原子操作,非 volatile 实例使用常规操作),并且想知道我是否以正确的方式进行处理。到
我正在为 Cortex-M0 CPU 和 gcc 编写代码。我有以下结构: struct { volatile unsigned flag1: 1; unsigned flag2: 1
我是一名优秀的程序员,十分优秀!