是否所有全局变量都是volatile限定的?
Should ALL global variables be volatile-qualified?
在这个例子中,正确性是否要求将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。即使这样,如果有适当的互斥,也不需要它。通常情况下,如果需要互斥全局变量,则可能会有一个糟糕的设计。
编辑:使它易失性并不意味着全局变量将是线程安全的!其他典型的使用可能是在以不寻常的方式访问内存的地方-例如,如果您在嵌入式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..)
- 在全局变量中保存类的实例以重新创建类(创建"backup")
- 当vector是tje全局变量时,c++中vector的内存管理
- std::threads可以从Windows DLL中的全局变量创建/销毁吗?
- 内联函数中具有内部链接的全局变量
- 如何在信号处理程序和普通函数中对全局变量进行互斥读写操作
- 全局变量 多读取器 一个写入器多线程安全?
- 如果全局变量默认是外部变量,为什么要添加"extern"关键字?
- 不同作用域中的静态变量和全局变量
- C++ 在编译时具有函数计算全局变量
- 修改程序的入口点时未调用全局变量的构造函数
- 使用 std::ios_base::Init 正确初始化全局变量
- 为什么我的全局变量似乎没有变化?
- C ++程序如何返回我的数组或写入全局变量
- 为什么我的数组值与此处的全局变量不匹配?
- QT C++中对全局变量的未定义引用
- 跨多个类的全局变量而不会出现重定义错误?
- 赋予全局变量而不是局部变量优先级的函数 - (异常行为)
- C++线程不检测全局变量更改
- 在 elf 文件中查找全局变量的位置
- 是否所有全局变量都是volatile限定的?