structc99side-effectspc-lint

Volatile struct member assignment: side effects / undefined behavior


I am considering the following code:

#include <stdint.h>

typedef struct {
   uint32_t a;
} foo_t;

volatile foo_t foo;
volatile uint32_t b;

int main(void)
{
    foo.a = b;
    return 0;
}

In this sample code, the PC-Lint Plus will complain about the assignment with 931 diagnostic message:

Note 931: both sides have side effects
Indicates when both sides of an expression have side-effects. An example is n++ + f(). This is normally benign. The really troublesome cases such as n++ + n are caught via Warning 564.
Supports CERT C EXP10-C - Do not depend on the order of evaluation of subexpressions or the order in which side effects take place.
Supports MISRA C 2012 Rule 1.3
Supports MISRA C 2004 Rule 1.2

Despite I am aware about side effects and sequence points , I cannot understand why this assignment triggers the message.
I see there are two side effects (write foo.a, read b), but they are related with different objects, so there is no undefined behavior according to https://en.cppreference.com/w/c/language/eval_order (emphasize by me):

  1. If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.
  2. If a side effect on a scalar object is unsequenced relative to a value computation using the value of the same scalar object, the behavior is undefined.

Additionally:
If b is non-volatile, there is no warning.
If only the struct member is volatile (not whole struct), there is no warning.

Is such an assignment really risky for some reason from a C99 standard point of view?


Solution

  • IMHO this is a reasonable warning. You are correct that the behavior is well-defined, and even that there's only one sensible physical behavior, which is essentially:

    foo.a = b;
      // must mean precisely
    STORE_INTO(foo.a, LOAD_VALUE_OF(b));
    

    However, there are two side-effects on that line; and it would indeed be much clearer to write each individual side-effect on an individual line—

    uint32_t temp = LOAD_VALUE_OF(b);
    STORE_INTO(foo.a, temp);
    

    This makes it clear that, for example, it is possible to context-switch to a different thread in between the first side-effect and the second: they do not happen atomically. So, rewrite your volatile code as—

    uint32_t temp = b;  // load
    foo.a = temp;  // store
    

    and both the code reviewer and PC-Lint will become happier.


    You write: "If only the struct member is volatile (not whole struct), there is no warning." Like this?—

    typedef struct {
       volatile uint32_t a;
    } foo_t;
    
    foo_t foo;
    volatile uint32_t b;
    
    int main(void)
    {
        foo.a = b;
        return 0;
    }
    

    If PC-Lint warns for your original code but not for the above code, I'd simply call that a bug in PC-Lint. ...And yep, I confirm that PC-Lint 2.0 (as of November 2022) has that bug. The code above should give warning 931, and yet it doesn't.