cgccoptimizationvolatilec11

Does assigning a volatile variable to itself suppress compiler optimization in a loop?


I'm working on a delay loop in C and come across an odd case with volatile. Consider this code:

void delay(unsigned int count){
    volatile unsigned int timer = count;
    while (timer > 0){
        timer = timer;
        timer--;
    }
}
int main(){
    delay(1000);
    return 0;
}

I added timer = timer to force the compiler to treat timer as volatile and prevent the loop from being optimized away.

Does self-assignment of a volatile variable guarantee that the compiler generates a read/write operation each time, or can it still optimize this out? I'm using GCC 11.4.0 on Linux System, but I'd like a general C11 answer if possible.

I've seen volatile used for empty loop (e.g., while (--timer); ), but couldn't find anything about self-assignment specifically. Does the C standard say anything about this?


Solution

  • Accessing (reading or writing) a volatile-qualified variable is always a "needed side-effect" (C23 5.2.2.4) so it can't be optimized away. Furthermore it can't be re-ordered but the compiler must respect the sequencing rules.

    The assignment operator(s) specifically state that "The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands." (C23 6.5.17.1) So timer=timer; should be well-defined. It forces both a read and a write.

    However, it is kind of pointless since timer; or (void)timer; alone is enough - this is a volatile read access which alone is something the compiler isn't allowed to optimize out. Self-documenting code often does something like this instead, since it is less strange-looking:

    int dummy = my_volatile; // read access that can't be optimized out
    (void)dummy; // dummy is never used
    

    As for your somewhat obscure loop, it can be rewritten much more readable:

    for(volatile unsigned int i=0; i<count; i++)
    {}
    

    Since this involves both repeated volatile writes and a volatile reads, nothing in this loop can be optimized out.

    Unrelated to your question, these kind of "busy wait delays" are almost certainly the wrong solution to any problem you can encounter in any context: