是否所有全局变量都是volatile限定的?

Should ALL global variables be volatile-qualified?

本文关键字:volatile 全局变量 是否      更新时间:2023-10-16

在这个例子中,正确性是否要求将global_value声明为volatile ?

int global_value = 0;
void foo () {
    ++ global_value;
}
void bar () {
    some_function (++global_value);
    foo ();
    some_function (++global_value);
}

我的理解是volatile是"有意的"。对于指向映射内存的指针和可以通过信号修改的变量(强调而不是用于线程安全),但是很容易想象bar可能编译成这样的东西:

push EAX
mov EAX, global_value
inc EAX
push EAX
call some_function
call foo
inc EAX
push EAX
call some_function
mov global_value, EAX
pop EAX

这显然是不正确的,但即使没有volatile,我相信根据C抽象机它是有效的。我错了吗?

如果是这样,在我看来,volatile通常被忽视。这不是什么新鲜事!


扩展示例

void baz (int* i) {
    some_function (++*i);
    foo ();
    some_function (++*i);
}
int main () {
    baz (&global_value);
}

即使bar保证编译成正确的don -cache-global_value实现,baz是否同样正确,或者是否允许缓存*i的非易失性值?

不,volatile关键字在这里是不必要的。因为global_value在函数bar之外是可见的,所以编译器不能假设它在调用另一个函数时保持不变。

[Update 2011-07-28]我找到了一个很好的引用来证明这一切。它在ISO C99, 5.1.2.3p2中,我懒得在这里完整地复制。它说:

在执行顺序中称为序列点的某些指定点上,先前评价的所有副作用应完成,并且不应发生后续评价的副作用。

序列点包括:

  • 在参数被求值后调用函数(6.5.2.2)。
  • 完整表达式的结尾:[…]表达式语句中的表达式(6.8.3);[…]

这就是你的证据。

volatile的唯一用途包括longjmp、信号处理程序、内存映射设备驱动程序和编写自己的低级多线程同步原语。然而,对于最后一种用法,volatile是不够的,甚至可能不是必需的。您肯定还需要asm(或特定于编译器或C1x原子)来进行同步。

volatile对于任何其他目的都没有用处,包括您所询问的代码。

正如Roland所说,我不确定应该引用标准的哪一部分来说明,"如果一个程序修改了一些东西,那就意味着对象在抽象机器中被修改了。"如果一个程序使用了一个值,那就意味着它使用了该对象在抽象机器中的任何值。

volatile控制读写内存的数量和顺序,但即使没有volatile,缓存值作为优化的实现也必须尊重抽象机器的行为。这就是"as-if"规则所说的,所以不服从的优化对我来说并不"容易想象";-)你提出的发出的代码对我来说显然是错误的,就像说"写可能会在没有更新或弄脏L1缓存的情况下进入内存,所以将来的读取仍然会看到缓存中的旧值"一样。不是在单核上,它不会,因为这样的缓存会被破坏。

如果你调用strcpy,然后检查目标缓冲区的内容,编译器不允许通过使用存储在寄存器中的字节的先前值来"优化"。strcpy不需要volatile char *。同理,global_value也不需要是volatile

我猜想在多线程代码中,"and then"(即读是否发生在写"之后"并因此"看到"新值)可能是由同步原语定义的。在某些实现中,由于实现特定的保证,volatile与同步有关。

在单线程代码中,以及在C和c++标准中,"and then"是由序列点定义的,在给出的代码中有很多序列点。

全局变量不应该总是声明为volatile。

只有当它可能被其他线程更改,并且可能遭受内存重排序问题或编译器指令重排序时,才真正需要它是volatile。即使这样,如果有适当的互斥,也不需要它。通常情况下,如果需要互斥全局变量,则可能会有一个糟糕的设计。

编辑:使它易失性并不意味着全局变量将是线程安全的!

其他典型的使用可能是在以不寻常的方式访问内存的地方-例如,如果您在嵌入式micro上有一些DMA映射内存。

在本例中不需要Volatile。例如,如果some_function()输出一些东西,asm清单似乎改变了c++机器的可观察行为,违反了标准。

我猜这是一个编译器错误。下面是GCC汇编器输出:

.cfi_def_cfa_register 5
subl    $24, %esp
.loc 1 67 0
movl    global_value, %eax
addl    $1, %eax
movl    %eax, global_value
movl    global_value, %eax
movl    %eax, (%esp)
call    _Z13some_functioni
.loc 1 68 0
call    _Z3foov
.loc 1 69 0
movl    global_value, %eax
addl    $1, %eax
movl    %eax, global_value
movl    global_value, %eax
movl    %eax, (%esp)
call    _Z13some_functioni
.loc 1 70 0
leave
.cfi_restore 5

global_value按预期在函数调用之间重新加载

volatile 也是用于线程安全的,简单的v-qualifier 对于所有情况下的线程安全都是不够的(有时您需要额外注意原子性和内存屏障,但是线程间通信变量应该是volatile的…

[编辑]:…如果它们被重复读取,并且在读取之间可能被另一个线程更改。但是,如果使用了任何同步锁(互斥锁等),则不是,因为锁保证变量不能被任何并发活动更改)。(感谢R..)