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):
- If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.
- 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?
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.