cembeddedmicrocontrollervolatilecpu-registers

How to force an unused memory read in C that won't be optimized away?


Microcontrollers often require a register to be read to clear certain status conditions. Is there a portable way in C to ensure that a read is not optimized away if the data is not used? Is it sufficient that the pointer to the memory mapped register is declared as volatile? In other words, would the following always work on standard compliant compilers?

void func(void)
{
   volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;

   *REGISTER;
}

I understand that dealing with functionality like this runs into compiler-dependent issues. So, my definition of portable is a bit loose in this case. I just mean that it would work as widely as possible with the most popular toolchains.


Solution

  • People argue quite strenuously about exactly what volatile means. I think most people agree that the construct you show was intended to do what you want, but there is no general agreement that the language in the C standard actually guarantees it as of C99. (The situation may have been improved in C2011; I haven't read that yet.)

    A nonstandard, but fairly widely supported by embedded compilers, alternative that may be more likely to work is

    void func(void)
    {
      asm volatile ("" : : "r" (*(unsigned int *)0x12345678));
    }
    

    (The 'volatile' here appies to the 'asm' and means 'this may not be deleted even though it has no output operands. It is not necessary to put it on the pointer as well.)

    The major remaining drawback of this construct is that you still have no guarantee that the compiler will generate a one-instruction memory read. With C2011, using _Atomic unsigned int might be sufficient, but in the absence of that feature, you pretty much have to write a real (nonempty) assembly insert yourself if you need that guarantee.

    EDIT: Another wrinkle occurred to me this morning. If reading from the memory location has the side-effect of changing the value at that memory location, you need

    void func(void)
    {
      unsigned int *ptr = (unsigned int *)0x12345678;
      asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
    }
    

    to prevent mis-optimization of other reads from that location. (To be 100% clear, this change will not change the assembly language generated for func itself, but may affect optimization of surrounding code, particularly if func is inlined.)