[edit] For background reading, and to be clear, this is what I 开发者_StackOverflowam talking about: Introduction to the volatile keyword
When reviewing embedded systems code, one of the most common errors I see is the omission of volatile for thread/interrupt shared data. However my question is whether it is 'safe' not to use volatile
when a variable is accessed via an access function or member function?
A simple example; in the following code...
volatile bool flag = false ;
void ThreadA()
{
...
while (!flag)
{
// Wait
}
...
}
interrupt void InterruptB()
{
flag = true ;
}
... the variable flag
must be volatile to ensure that the read in ThreadA is not optimised out, however if the flag were read via a function thus...
volatile bool flag = false ;
bool ReadFlag() { return flag }
void ThreadA()
{
...
while ( !ReadFlag() )
{
// Wait
}
...
}
... does flag
still need to be volatile? I realise that there is no harm in it being volatile, but my concern is for when it is omitted and the omission is not spotted; will this be safe?
The above example is trivial; in the real case (and the reason for my asking), I have a class library that wraps an RTOS such that there is an abstract class cTask that task objects are derived from. Such "active" objects typically have member functions that access data than may be modified in the object's task context but accessed from other contexts; is it critical then that such data is declared volatile?
I am really interested in what is guaranteed about such data rather than what a practical compiler might do. I may test a number of compilers and find that they never optimise out a read through an accessor, but then one day find a compiler or a compiler setting that makes this assumption untrue. I could imagine for example that if the function were in-lined, such an optimisation would be trivial for a compiler because it would be no different than a direct read.
My reading of C99 is that unless you specify volatile
, how and when the variable is actually accessed is implementation defined. If you specify volatile
qualifier then code must work according to the rules of an abstract machine.
Relevant parts in the standard are: 6.7.3 Type qualifiers
(volatile description) and 5.1.2.3 Program execution
(the abstract machine definition).
For some time now I know that many compilers actually have heuristics to detect cases when a variable should be reread again and when it is okay to use a cached copy. Volatile makes it clear to the compiler that every access to the variable should be actually an access to the memory. Without volatile it seems compiler is free to never reread the variable.
And BTW wrapping the access in a function doesn't change that since a function even without inline
might be still inlined by the compiler within the current compilation unit.
P.S. For C++ probably it is worth checking the C89 which the former is based on. I do not have the C89 at hand.
Yes it is critical.
Like you said volatile
prevents code breaking optimization on shared memory [C++98 7.1.5p8]
.
Since you never know what kind of optimization a given compiler may do now or in the future, you should explicitly specify that your variable is volatile.
Of course, in the second example, writing/modifying variable 'flag' is omitted. If it is never written to, there is no need for it being volatile.
Concerning the main question
The variable has still to be flagged volatile even if every thread accesses/modifies it through the same function.
A function can be "active" simultaneously in several threads. Imagine that the function code is just a blueprint that gets taken by a thread and executed. If thread B interrupts the execution of ReadFlag in thread A, it simply executes a different copy of ReadFlag (with a different context, e.g. a different stack, different register contents). And by doing so, it could mess up the execution of ReadFlag in thread A.
In C, the volatile
keyword is not required here (in the general sense).
From the ANSI C spec (C89), section A8.2 "Type Specifiers":
There are no implementation-independent semantics for
volatile
objects.
Kernighan and Ritchie comment on this section (referring to the const
and volatile
specifiers):
Except that it should diagnose explicit attempts to change
const
objects, a compiler may ignore these qualifiers.
Given these details, you can't be guaranteed how a particular compiler interprets the volatile
keyword, or if it ignores it altogether. A keyword that is completely implementation dependent shouldn't be considered "required" in any situation.
That being said, K&R also state that:
The purpose of
volatile
is to force an implementation to suppress optimization that could otherwise occur.
In practice, this is how practically every compiler I have seen interprets volatile
. Declare a variable as volatile
and the compiler will not attempt to optimize accesses to it in any way.
Most of the time, modern compilers are pretty good about judging whether or not a variable can be safely cached or not. If you find that your particular compiler is optimizing away something that it shouldn't, then adding a volatile
keyword might be appropriate. Be aware, though, that this can limit the amount of optimization that the compiler can do on the rest of the code in the function that uses the volatile
variable. Some compilers are better about this than others; one embedded C compiler I used would turn off all optimizations for a function that accesses a volatile
, but others like gcc seem to be able to still perform some limited optimizations.
Accessing the variable through an accessor function should prevent the function from caching the value. Even if the function is auto-inlined, each call to the function should re-call the function and re-fetch a new value. I have never seen a compiler that would auto-inline the accessor function and then optimize away the data re-fetch. I'm not saying it can't happen (since this is implementation-dependent behavior), but I wouldn't write any code that expects that to happen. Your second example is essentially placing a wrapper API around the variable, and libraries do this without using volatile
all the time.
All in all, the treatment of volatile
objects in C is implementation-dependent. There is nothing "guaranteed" about them according to the ANSI C89 spec.
Your code is sharing the volatile
object between a thread and an interrupt routine. No compiler implementation (that I have ever seen) gives volatile
enough power to be sufficient for handling parallel access. You should use some sort of locking mechanism to guarantee that the two threads (in your first example) don't step on each other's toes (even though one is an interrupt handler, you can still have parallel access on a multi-CPU or multi-core system).
Edit: I didn't read the code very closely and so I thought this was a question about thread synchronization, for which volatile
should never be used, however this usage looks like it might be OK (Depending on how else the variable in question is used, and if the interrupt is always running such that it's view of memory is (cache-)coherent with the one that the thread sees. In the case of 'can you remove the volatile
qualifier if you wrap it in a function call?' the accepted answer is correct, you cannot. I'm going to leave my original answer because it's important for people reading this question to know that volatile
is almost useless outside of certain very special cases.
More Edit: Your RTOS use case may require additional protection above and beyond volatile, you may need to use memory barriers in some cases or make them atomic... I can't really tell you for sure, it's just something you need to be careful of (I'd suggest looking at the Linux kernel documentation link I have below though, Linux doesn't use volatile
for that kind of thing, very probably with a good reason). Part of when you do and do not need volatile
depends very strongly on the memory model of the CPU you're running on, and often volatile
is not good enough.
volatile
is the WRONG way to do this, it does NOT guarantee that this code will work, it wasn't meant for this kind of use.
volatile
was intended for reading/writing to memory mapped device registers, and as such it is sufficient for that purpose, however it DOES NOT help when you're talking about stuff going between threads. (In particular the compiler is still aloud to re-order some reads and writes, as is the CPU while it's executing (this one's REALLY important since volatile
doesn't tell the CPU to do anything special (sometimes it means bypass cache, but that's compiler/CPU dependent))
see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html, Intel developer article, CERT, Linux kernel documentation
Short version of those articles, volatile
used the way you want to is both BAD and WRONG. Bad because it will make your code slower, wrong because it doesn't actually do what you want.
In practice, on x86 your code will function correctly with or without volatile
, however it will be non-portable.
EDIT: Note to self actually read the code... this is the sort of thing volatile
is meant to do.
精彩评论