在此示例中,正确性是否需要global_value
声明volatile
?
int global_value = 0;
void foo () {
++ global_value;
}
void bar () {
some_function (++global_value);
foo ();
some_function (++global_value);
}
我的理解是volatile
是“意图”用于pointers to mapped memory and variables which can be modified by signals(并强调不用于线程安全),但很容易想象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
经常被忽视。这将是nothing new!
void baz (int* i) {
some_function (++*i);
foo ();
some_function (++*i);
}
int main () {
baz (&global_value);
}
即使bar
保证编译成正确的dont-cache-global_value实现,baz
也同样正确,或者是否允许缓存{{1}的非易失性值}?
答案 0 :(得分:10)
不,此处不需要volatile
关键字。由于global_value
在函数bar
之外是可见的,因此如果调用另一个函数,编译器不能假定它保持不变。
[更新2011-07-28]我发现了一个很好的引用,证明了这一切。它是在ISO C99,5.1.2.3p2中,我太懒了,不能完全复制到这里。它说:
在执行序列中称为序列点的某些特定点,先前评估的所有副作用都应完整,并且不会产生后续评估的副作用。
序列点包括:
你有证据。
答案 1 :(得分:5)
volatile
的唯一用途涉及longjmp
,信号处理程序,内存映射设备驱动程序以及编写自己的低级多线程同步原语。然而,对于最后一次使用,volatile
是不够的,甚至可能不是必需的。你肯定还需要asm(或编译器特定的或C1x原子)来进行同步。
volatile
对任何其他用途都没有用,包括您询问的代码。
答案 2 :(得分:4)
正如Roland所说,我不确定要引用的标准的哪一部分,“如果一个程序修改某些东西,那意味着该对象在抽象机器中被修改。如果一个程序使用一个值,那意味着它使用对象在抽象机器中的任何值“。
volatile
控制对内存的读写次数和顺序,但即使没有volatile
,将值缓存为优化的实现也必须尊重抽象机的行为。这就是“假设”规则所说的,所以那些不服从的优化对我来说并不是“容易想象”;-)你提出的发出的代码对我来说显然是错误的,“写作可能会发生内存没有更新或弄脏L1缓存,因此未来的读取仍然会看到缓存中的旧值“。不是在单个核心上,它不会,因为表现得像那样的缓存会被破坏。
如果调用strcpy
,然后检查目标缓冲区的内容,则不允许编译器通过使用存储在寄存器中的该字节的先前值来“优化”。 strcpy
不会volatile char *
。同样,global_value
不一定是volatile
。
我认为混淆可能是在多线程代码中,“然后”,也就是说,读取是在“写入”之后发生并因此“看到”新值,是由同步原语定义的。在某些实现中,由于特定于实现的保证,volatile
与同步有关。
在单线程代码中,在C和C ++标准中,“然后”由序列点定义,在给定的代码中有很多。
答案 3 :(得分:1)
没有。全局变量不应总是被声明为volatile。
如果可能被其他线程更改并且可能遭受内存重新排序问题或编译器指令重新排序,那么您只需要它是易失性的。即使这样,如果你有适当的静音,你也不会需要它。但通常情况下,如果需要互斥全局变量,则可能设计不良。
编辑:使它变得不稳定并不意味着全局变量虽然是线程安全的!
其他典型用途可能是以不寻常的方式访问内存 - 例如,如果在嵌入式微型计算机上有一些DMA映射内存。
答案 4 :(得分:0)
此示例中不需要易失性。例如,如果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按预期在函数调用
之间重新加载此外,对于线程安全而言, ,在所有情况下,v-qualifier对于线程安全来说不足(您有时需要额外关注原子性和内存障碍但是,互通传播变量应该易变...
[已编辑]: ...如果它们被重复读取,可能会被读取之间的另一个线程更改。但是,如果使用任何同步锁(互斥锁等),则不,因为锁保证变量不能被任何并发活动更改) (感谢R ..)