Home > Blockchain >  Is read to local volatile variable always guaranteed in C?
Is read to local volatile variable always guaranteed in C?

Time:08-19

Is read to local volatile variable always guaranteed?

I have a function that performs some operation on struct members. Struct members are not volatile, and may change (embedded application, interrupts) while function operates. It is no a problem if values change during read (copy-to-local variable), but they shall not change while doing multiple if statements.

#include <string.h>
#include <stdio.h>

typedef struct {
    int wptr;
    int rptr;
} rw_t;

void
use_it(const rw_t* rw) {
    volatile int r, w;

    /* It is OK if rw->rptr or rw->wptr get changed during these copy operation */
    r = rw->rptr;
    w = rw->wptr;

    /* I must ensure that r and w are actually local references,
       therefore a copy to local shall be done first */
    if (r > w) {
        printf("R is more than W\r\n");
    } else if (r < w) {
        printf("W is more than R\r\n");
    } else {
        printf("R and W are equal\r\n");
    }
}

Compiling with ARM-GCC none-eabi 10.2.1 with -Os seems to work properly. But is this guaranteed to always work with any compiler and any optimization?

Minimal reproduceable example: https://godbolt.org/z/nvocrsrzE

CodePudding user response:

If you aim to pass a volatile qualified pointer to the function, then the function's parameter must be volatile rw_t* rw or volatile const rw_t* rw. Not using volatile would be problematic because then the compiler may assume that the pointed-at data will never change and therefore optimise out the local variables, since it might as well read directly from the original location in that case.

The local variables shouldn't need to be volatile however, since there is no reason why the compiler needs to re-read them after the first assignment. You don't want them to be updated so why volatile? A local variable which is not volatile will not get suddenly updated in the middle of an if - else chain and that's fully portable behavior. Whereas a volatile variable might change at any time.

Please note however that volatile does not guarantee atomic access. The values might be updated between r = rw->rptr; and w = rw->wptr; so that r ends up containing an old value but w a new one.

CodePudding user response:

It does not matter if an object has automatic, static or dynamic storage duration. volatile shows the compiler that object is side effects prone and it will be read from its storage location every time it is used and stored every time it is modified.

int foo(volatile int x)
{
    return x x x x;
}


foo:
        sub     sp, sp, #8
        str     r0, [sp, #4]
        ldr     r3, [sp, #4]
        ldr     r1, [sp, #4]
        ldr     r2, [sp, #4]
        add     r3, r3, r1
        ldr     r0, [sp, #4]
        add     r3, r3, r2
        add     r0, r3, r0
        add     sp, sp, #8
        bx      lr

But in your case you need something completely different

void
use_it(volatile const rw_t* rw) {
    int r, w;

    r = rw->rptr;  //guaranteed to read from memory
    w = rw->wptr;

//further usage guaranteed to be using local r & w

use_it:
        ldr     r2, [r0, #4]  //reads from memory
        ldr     r3, [r0]      //reads from memory
        cmp     r2, r3        //from now uses local variables (stored in registers is optimizations are on
        push    {r4, lr}
        bgt     .L9
        ldrlt   r0, .L10
        ldrge   r0, .L10 4
        bl      puts
        pop     {r4, lr}
        bx      lr

use_it: (cortex-M4 thumb version)
        ldr     r2, [r0, #4]
        ldr     r3, [r0]
        cmp     r2, r3
        bgt     .L7
        bge     .L6
        ldr     r0, .L8
        b       puts
.L6:
        ldr     r0, .L8 4
        b       puts
.L7:
        ldr     r0, .L8 8
        b       puts
  •  Tags:  
  • c
  • Related