clinuxcompiler-optimization

compiler optimization issue / variable has wrong value


I define variables as

volatile sig_atomic_t v1;
volatile int v2;

Then in one process (thread) perform

v1 = false;
v2 = 0;
...
v2 = (some_var);
v1 = true;

and in another process (thread)

while(!v1) { (doing some tasks not related to v1 and v2); }
if(v2 == 0) { (do something); }

The problem I get is v2 sometimes set to 0 when while ends. while is proven to finish when v1 becomes true. (some_var) is proven to be non-zero. Using gcc.

What do I miss here?


Solution

  • volatile has nothing to do with atomic access.

    Preventing the compiler from doing strange optimizations when it doesn't realize that a callback is called from the OS not a program will only get you so far - in fact PC-like compilers like gcc are smart enough to know that callbacks may be called, so volatile on variables shared between threads probably doesn't add anything meaningful to the average gcc Linux application. (It would in case your target is some more or less dysfunctional embedded systems compiler though.)

    However, there is still the core issue of re-entrancy and protection against race condition bugs. Check out for example Using volatile in embedded C development - that's about interrupt service routines but they behave just like threads when it comes to re-entrancy. Swap "ISR" for thread in my answer there, and emphasis on the last sentence:

    When writing C, all communication between an ISR and the background program must be protected against race conditions. Always, every time, no exceptions. The size of the MCU data bus does not matter, because even if you do a single 8 bit copy in C, the language cannot guarantee atomicity of operations. Not unless you use the C11 feature _Atomic. If this feature isn't available, you must use some manner of semaphore or disable the interrupt during read etc. Inline assembler is another option. volatile does not guarantee atomicity.

    What can happen is this:

    • Load value from stack into register
    • Interrupt occurs
    • Use value from register

    And then it doesn't matter if the "use value" part is a single instruction in itself. Sadly, a significant portion of all embedded systems programmers are oblivious to this, probably making it the most common embedded systems bug ever. Always intermittent, hard to provoke, hard to find.

    Feels like I've written hundreds of answers about this. Here's another:

    What volatile does:

    • Guarantees an up-to-date value in the variable, if the variable is modified from an external source (a hardware register, an interrupt, a different thread, a callback function etc).
    • Blocks all optimizations of read/write access to the variable.
    • Prevent dangerous optimization bugs that can happen to variables shared between several threads/interrupts/callback functions, when the compiler does not realize that the thread/interrupt/callback is called by the program. (This is particularly common among various questionable embedded system compilers, and when you get this bug it is very hard to track down.)

    What volatile does not:

    • It does not guarantee atomic access or any form of thread-safety.
    • It cannot be used instead of a mutex/semaphore/guard/critical section. It cannot be used for thread synchronization.

    What volatile may or may not do:

    • It may or may not be implemented by the compiler to provide a memory barrier, to protect against instruction cache/instruction pipe/instruction re-ordering issues in a multi-core environment. You should never assume that volatile does this for you, unless the compiler documentation explicitly states that it does.

    Regarding the memory barrier part and different possible ways to read/misinterpret the C standard:
    Why does memory-barrier prohibit optimization on static global variable?