Although in practice that’s true it may not be in theory. When a variable is not volatile, the compiler may decide to optimize the code. For example
int count;
thread1 () {
while ( foo > 5 ) {
count++;
}
printf(“count %d\n”, count );
}
thread2 ()
{
if ( count > 1000 ) {
printf(“timeout\n”);
}
}
In theory the compiler could look at thread1() and say oh, let’s optimize it (compiler doesn’t really know it’s a thread). Instead of accessing the value from memory every time. I will do this:
int count;
thread1 () {
//Load value from memory and put it in ebx register
while ( value == 1 ) {
count++; // increment ebx, this is faster then reading/writing to memory
}
printf(“count %d\n”, count ); // use ebx again
//Now as the fonction prepares to return it put the ebx value back in memory we are out the compiler will store eax into the count global variable.
}
That optimisation would make thread1() work perfectly and much faster (Saves a memory read and a memory read per loop).
Thread2() will only see count being greater then 1000 when the while loop of thread 1 is terminates. So it may appear like it’s working but in fact it’s not
In real life though this kind of optimisation rarely happens. Typicaly there would be a fonction call in the while loop and if the fonction call is in a different file or a library, the compiler can’t detect if count is access or not and thus will play it safe and assume it could be change by the fonction.
This can get even trickier if you put in pointer and pointer aliases So it comes down to: don’t really on specific compiler behavior, really on what the C or C++ code language says.
The rules says if a variable can be access by two or more routines from different context (threads/interrupt/variable in share mem etc) (imagine this can get worse if you run on multi-cpu/core) you must use volatile.
play by the rules and you will be safe (well almost), volatile doesn’t solve all problemes. They only apply to atomic type, for example volatile on long long (int64) on 32 bit OS are useless.