问题
In this example, does correctness require global_value
to be declared volatile
?
int global_value = 0;
void foo () {
++ global_value;
}
void bar () {
some_function (++global_value);
foo ();
some_function (++global_value);
}
My understanding is that volatile
is "intended" for pointers to mapped memory and variables which can be modified by signals (and emphatically not for thread-safety) but it's easy to imagine that bar
might compile to something like this:
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
This is clearly not correct, but even without volatile
I believe it is valid according to the C abstract machine. Am I wrong or is it valid?
If so, it seems to me that volatile
is routinely overlooked. This would be nothing new!
Extended Example
void baz (int* i) {
some_function (++*i);
foo ();
some_function (++*i);
}
int main () {
baz (&global_value);
}
Even if bar
is guaranteed to compile into a correct dont-cache-global_value implementation, will baz
be similarly correct, or is it allowed to cache the non-volatile value of *i
?
回答1:
No, the volatile
keyword is not necessary here. Since global_value
is visible outside the function bar
, the compiler must not assume that it remains the same if another function is called.
[Update 2011-07-28] I found a nice citation that proves it all. It's in ISO C99, 5.1.2.3p2, which I am too lazy to copy here in its entirety. It says:
At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
Sequence points include:
- The call to a function, after the arguments have been evaluated (6.5.2.2).
- The end of a full expression: [...] the expression in an expression statement (6.8.3); [...]
There you have your proof.
回答2:
The only uses of volatile
involve longjmp
, signal handlers, memory-mapped device drivers, and writing your own low-level multi-threaded synchronization primitives. For this last use however, volatile
is not sufficient and may not even be necessary. You'll definitely also need asm (or compiler-specific or C1x atomics) for synchronization.
volatile
is not useful for any other purposes, including the code you asked about.
回答3:
As Roland says, I'm not sure what part of the standard to cite to say, "if a program modifies something, that means the object is modified in the abstract machine. If a program uses a value, that means it uses whatever value the object has in the abstract machine".
volatile
controls the number and order of reads and writes to memory, but even without volatile
, an implementation that caches values as an optimization must respect the behavior of the abstract machine. That's what the "as-if" rule says, so optimizations that don't obey that aren't "easy to imagine" for me ;-) Your proposed emitted code is as clearly wrong to me as saying, "a write might go to memory without updating or dirtying the L1 cache, so future reads will still see the old value in the cache". Not on a single core, it won't, because a cache that behaved like that would be broken.
If you call strcpy
, and then examine the contents of the destination buffer, the compiler isn't allowed to "optimize" by using a prior value of that byte, stored in a register. strcpy
doesn't take a volatile char *
. Similarly, global_value
does not need to be volatile
.
I suppose the confusion may be that in multi-threaded code, "and then", which is to say whether the read occurs "after" the write and hence "sees" the new value, is defined by synchronization primitives. In some implementations, volatile
has something to do with synchronization due to implementation-specific guarantees.
In single-threaded code, and in the C and C++ standards, "and then" is defined by sequence points, of which there are plenty in the code given.
回答4:
No. Global variables should not always be declared volatile.
You only really need it to be volatile if it could be changed by other threads and may suffer from memory reordering issues or compiler instruction reordering. And even then you won't need it if you have appropriate mutexing. Typically though, you probably have a bad design if you need to mutex global variables.
EDIT: making it volatile does not mean that the global variable would be thread safe though!
Other typical uses might be where the memory is accessed in an unusual way - for example if you have some DMA mapped memory on an embedded micro.
回答5:
Volatile isn't needed in this example. If, for instance, some_function() outputs something, the asm listing seems changes observable behaviour of the c++ machine and violates the standard.
I guess it is a compiler bug Here is GCC assembler output:
.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 is reloaded, as expected, between function calls
Also volatiles are for thread-safety too, simply v-qualifier is not sufficient for thread safety in all cases (you sometimes need additional care about atomicity and memory barriers, but interthread communication variables should be volatile...
[EDITED]: ... if they are repeatedly read and may be changed by another thread between reads. This, however, is not the case if any syncronization lock (mutex, etc) is used, since lock guarantees the variables can not be changed by any concurrent activity) (thanks to R..)
来源:https://stackoverflow.com/questions/6858247/should-all-global-variables-be-volatile-qualified