Search code examples
cfunctionargumentsvolatile

volatile argument to a function?


This question has a comment by @ideasman42 that says:

Not sure its worth another question, but interested to know why you might write void func(int volatile arg);

I thought it was worth a question, but didn't see one, so here it is. What, if any, is the effect of doing that?


What sparked it for me, was writing a slightly different set of wrapper functions than what the Raspberry Pi Pico SDK provides. Specifically:

io_rw_32 _i2s_get_hw_clkdiv(struct _i2s* i2s)
{
    return i2s->pio_ch->sm[i2s->pio_sm].clkdiv;
}

void _i2s_set_hw_clkdiv(struct _i2s* i2s, io_rw_32 clkdiv)
{
    i2s->pio_ch->sm[i2s->pio_sm].clkdiv = clkdiv;
}

A header file buried deep in the SDK says typedef volatile uint32_t io_rw_32;.

Sure, I could do

uint32_t _i2s_get_hw_clkdiv(struct _i2s* i2s)
{
    return i2s->pio_ch->sm[i2s->pio_sm].clkdiv;
}

void _i2s_set_hw_clkdiv(struct _i2s* i2s, uint32_t clkdiv)
{
    i2s->pio_ch->sm[i2s->pio_sm].clkdiv = clkdiv;
}

instead, and (probably?) generate the exact same code, but if for some unforeseen reason that typedef changes, I'd rather have as few datatype conversions as possible.


Solution

  • What, if any, is the effect of doing that?

    It tells the compiler the value of the parameter must be reloaded each time it is used (or, if the source code modifies the parameter, the new value must be written each time).

    There is almost never a reason to do that. With const, it can be useful to declare a function parameter as const if you intend never to modify it. If you declare it const and modify it due to a typo where you intended some other name, the compiler will warn you. However, volatile is normally used for accessing special hardware, but function parameters are managed by the compiler and should not need volatile. One time when you might want to use volatile with a function parameter is when you are debugging and want to be able to change the parameter from the debugger. Then using volatile will ensure the generated code gets the new value each time it is used in the source code. Since this would only be for debugging, it should not be used in deployed code.

    … generate the exact same code…

    No, it does not. With a plain uint32_t parameter, Clang, with optimization enabled, does not reload the parameter from memory when it is used multiple times. When preparing for the second function call in this code:

    void foo0(uint32_t clkdiv)
    {
        bar(clkdiv);
        bar(clkdiv);
    }
    

    Clang copies the value of clkdiv from a register where it has cached it. In contrast, in this code:

    void foo1(io_rw_32 clkdiv)
    {
        bar(clkdiv);
        bar(clkdiv);
    }
    

    Clang reloads the parameter from memory. Interestingly, it is not memory where the parameter was passed to the function but rather is memory in the stack frame of the called function. The argument was passed to the function in a register, and the function stored it in memory to create the parameter. (A parameter is an object that is initialized with the value of an argument; it is not the argument itself.)

    It is a mistake for the code to be using a volatile type as a parameter, as it results in inefficient code generation.