Please have a look at this more or less standard example that i see people use when talking about the usage of volatile in the context of embedded c++ firmware development on a baremetal target:
// main.cpp
bool flag = false; // note: not volatile
void some_interrupt_routine()
{
// hw: no nested interrupts possible
flag = true;
}
void main()
{
while (true) {
// assume atomic reads/writes of "flag"
if (flag == true) {
flag = false;
// do something
}
}
}
Most, if not all, compilers with optimization enabled will replace the loop in main() with an unconditional endless loop that does nothing, if flag is not declared volatile. What happens if "do something" is a call to a function inside another compilation unit?
Please make the following assumptions:
- a gcc-like compiler with optimization enabled
- c++14 is used
- all cpp files are compiled separately into object files which are then linked into a final executable
- link time optimization is not enabled
- no hardware barrier, fence or other synchronisation instructions are available.
- no compiler barrier, fence or other synchronisation intrinsics are available, especially
__asm volatile("" ::: "memory"); std::atomicis not implemented by the compiler vendor- reads and writes to the variable "flag" are atomic
My Question is:
Is it safe to assume the compiler will not be able to optimize the loop as it can not know if the call to "do something" will change the value of "flag"?
Also: is it safe to assume, that the assembly code generated for the function "some_interrupt_routine" will always write to the variable "flag" as it is a global variable? My reasoning is that the compiler can't possibly know if code in another compilation unit might call some_interrupt_routine and check the value of flag afterwards. Of course the linker might remove the function from the final executable.
I did some tests and it seems my assumptions are right. I guess in this context it is not relevant that the value is changed in an interrupt routine or any other function inside another compilation unit.
I also did look at other related questions, but most of them either talk about:
- how volatile is not enough for synchronisation OR
- how you should use std::atomic for variables shared between application and interrupts OR
- how volatile should not be used at all but rather to use locking mechanisms provided by the os OR
- how volatile should be used exclusively for accessing memory mapped registers
Most of these points are certainly true, but i feel like they do not answer my question. Don't get me wrong, i would love to use std::atomic, but it is simply not available to me.
However, i might be wrong and just wanted to confirm this.