An embedded project I'm working on requires reading a specific location in memory, but the value of that memory location is not needed. Currently I am reading the volatile variable into a dummy variable as in foo1()
below, but I am curious about the method in foo2()
.
void foo1(void) {
volatile uint32_t *a = (volatile uint32_t *)0xdeadbeef;
volatile uint32_t discard = *a;
}
void foo2(void) {
volatile uint32_t *a = (volatile uint32_t *)0xdeadbeef;
*a;
}
See the dissassembly (compiled with gcc 4.7.2 and -O3):
foo1:
movl 0xdeadbeef, %eax
movl %eax, -0x4(%rsp)
ret
foo2:
movl 0xdeadbeef, %eax
ret
The method in foo2()
seems to work, but I want to know if it is guaranteed to work and isn't a side effect of the compiler version and optimizations I am using.
When we write to a volatile variable, it creates a happens-before relationship with each subsequent read of that same variable. So any memory writes that have been done until that volatile variable write, will subsequently be visible to any statements that follow the read of that volatile variable.
The volatile keyword is useful in two multi-threading scenarios: When only one thread writes to the volatile variable and other threads read its value. Thus, the reading threads see the latest value of the variable. When multiple threads are writing to a shared variable such that the operation is atomic.
1) Global variables modified by an interrupt service routine outside the scope: For example, a global variable can represent a data port (usually global pointer referred as memory mapped IO) which will be updated dynamically. The code reading data port must be declared as volatile in order to fetch latest data available at the port.
The field to read. The reference to T that was read. This reference is the latest written by any processor in the computer, regardless of the number of processors or the state of processor cache. For more information, see the Volatile class. Returns a 64-bit unsigned value, loaded as an atomic operation.
It's standard in C, but not C++. See: https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Volatiles.html
BUT, you certainly don't need to do the volatile write to discard
. These will probably all compile to the same as foo2:
volatile
qualifier from discard
; or!*a
or *a + 0 instead of *a
. The value must be accessed in order to evaluate the expression, even in C++The keyword volatile
tells the compiler an object might change outside the scope of the normal (i.e. visible by the compiler) program flow. Therefore, the compiler performs this access in general. The last sentence refers to how the access is performed, e.g. byte-read, unaligned reads, etc.
Futhermore, the compiler must execute all accesses to such objects in the order given by the program flow. Note, however, that it may reorder accesses to non-volatile objects freely and the underlying hardware might think different, too (the compiler might not know that).
The compiler might still optimize accesses to volatile
objects which exist and are modified only in the visible code. This is true for local variables where the address is not taken (there might be other situations), as these cannot be reached outside the scope. For the pointers you use, this is not true, as the compiler does not know about the object they point to.
To drop the result of an expression without compiler warning, just cast it to void
:
volatile uint32_t *vi = ...;
(void)*vi; // this still might be optimized if vi is local
(If the target is read-only, add const
.)
See the gcc documentation for details on volatile
accesses. An implementation which complies to the C standard has to provide this information.
Also note that the underlying hardware still might reorder the accesses, unless the memory area uses a strictly ordered/non-cached access policy. This is typical for memory-mapped peripheral registers. If a cache/MMU is used, the areas might have to be set up accordingly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With