Next: Floating Point Portability, Previous: Buffer Overruns, Up: Portable C and C++
The keyword volatile
is often misunderstood in portable code.
Its use inhibits some memory-access optimizations, but programmers often
wish that it had a different meaning than it actually does.
volatile
was designed for code that accesses special objects like
memory-mapped device registers whose contents spontaneously change.
Such code is inherently low-level, and it is difficult to specify
portably what volatile
means in these cases. The C standard
says, “What constitutes an access to an object that has
volatile-qualified type is implementation-defined,” so in theory each
implementation is supposed to fill in the gap by documenting what
volatile
means for that implementation. In practice, though,
this documentation is usually absent or incomplete.
One area of confusion is the distinction between objects defined with volatile types, and volatile lvalues. From the C standard's point of view, an object defined with a volatile type has externally visible behavior. You can think of such objects as having little oscilloscope probes attached to them, so that the user can observe some properties of accesses to them, just as the user can observe data written to output files. However, the standard does not make it clear whether users can observe accesses by volatile lvalues to ordinary objects. For example:
/* Declare and access a volatile object. Accesses to X are "visible" to users. */ static int volatile x; x = 1; /* Access two ordinary objects via a volatile lvalue. It's not clear whether accesses to *P are "visible". */ int y; int *z = malloc (sizeof (int)); int volatile *p; p = &y; *p = 1; p = z; *p = 1;
Programmers often wish that volatile
meant “Perform the memory
access here and now, without merging several memory accesses, without
changing the memory word size, and without reordering.” But the C
standard does not require this. For objects defined with a volatile
type, accesses must be done before the next sequence point; but
otherwise merging, reordering, and word-size change is allowed. Worse,
it is not clear from the standard whether volatile lvalues provide more
guarantees in general than nonvolatile lvalues, if the underlying
objects are ordinary.
Even when accessing objects defined with a volatile type,
the C standard allows only
extremely limited signal handlers: the behavior is undefined if a signal
handler reads any nonlocal object, or writes to any nonlocal object
whose type is not sig_atomic_t volatile
, or calls any standard
library function other than abort
, signal
, and (if C99)
_Exit
. Hence C compilers need not worry about a signal handler
disturbing ordinary computation, unless the computation accesses a
sig_atomic_t volatile
lvalue that is not a local variable.
(There is an obscure exception for accesses via a pointer to a volatile
character, since it may point into part of a sig_atomic_t
volatile
object.) Posix
adds to the list of library functions callable from a portable signal
handler, but otherwise is like the C standard in this area.
Some C implementations allow memory-access optimizations within each
translation unit, such that actual behavior agrees with the behavior
required by the standard only when calling a function in some other
translation unit, and a signal handler acts like it was called from a
different translation unit. The C standard hints that in these
implementations, objects referred to by signal handlers “would require
explicit specification of volatile
storage, as well as other
implementation-defined restrictions.” But unfortunately even for this
special case these other restrictions are often not documented well.
See When is a Volatile Object Accessed?, for some
restrictions imposed by GCC. See Defining Signal Handlers, for some
restrictions imposed by the GNU C library. Restrictions
differ on other platforms.
If possible, it is best to use a signal handler that fits within the limits imposed by the C and Posix standards.
If this is not practical, you can try the following rules of thumb. A
signal handler should access only volatile lvalues, preferably lvalues
that refer to objects defined with a volatile type, and should not
assume that the accessed objects have an internally consistent state
if they are larger than a machine word. Furthermore, installers
should employ compilers and compiler options that are commonly used
for building operating system kernels, because kernels often need more
from volatile
than the C Standard requires, and installers who
compile an application in a similar environment can sometimes benefit
from the extra constraints imposed by kernels on compilers.
Admittedly we are handwaving somewhat here, as there are few
guarantees in this area; the rules of thumb may help to fix some bugs
but there is a good chance that they will not fix them all.
For volatile
, C++ has the same problems that C does.
Multithreaded applications have even more problems with volatile
,
but they are beyond the scope of this section.
The bottom line is that using volatile
typically hurts
performance but should not hurt correctness. In some cases its use
does help correctness, but these cases are often so poorly understood
that all too often adding volatile
to a data structure merely
alleviates some symptoms of a bug while not fixing the bug in general.