为什么cppcheck的 MISRA检查会因为对一个volatile指针所指向的结构体的成员进行自增而发出警告?
我们正在使用cppcheck premium进行MISRA扫描,它针对这段代码发出警告:
(为保密信息,已改写)
#include <stdint.h>
typedef struct {
uint16_t foo;
uint16_t bar;
} Bassomatic;
void Bassomatic_update(volatile Bassomatic *pb) {
pb->bar++;
}
被标记的MISRA规则是13.3:
包含自增(++)或自减(--)运算符的完整表达式,不能再有除该自增或自减运算符引起的副作用之外的其他潜在副作用
这是一个真实的问题,还是cppcheck的一个错误?我们在这段代码里没有使用除自增/自减外的其他副作用,这段代码与 pb->bar += 1u 没有本质差别,后者反而更难阅读。
MISRA的关注点与涉及带volatile限定变量的读-修改-写竞态的并发问题分开;这部分我们通过其他方式处理。
解决方案
这是一个MISRA违规,其原因已在你贴出的引文中解释过——在同一表达式中不能有两个副作用。访问带volatile限定的变量本身就是一个副作用,对变量写入同样也是一个副作用。该规则的基本原理是,同一表达式中若存在多个未排序的副作用,将导致C 的未定义行为。在此并非问题,但在其他情况下可能会出现。
很可能 pb->bar++; 在你的应用中不会成为问题,但请考虑以下情形:在 x++ 的情况下,x 先被读取,然后增加1,接着写回。如果 x 是一个硬件寄存器,且在读取与写入之间内容发生变化,那么这次变化就会被覆盖。对于普通变量而言,这种情况不会发生(除非多线程且存在竞争条件),但对于可能在程序控制之外随时变化的硬件寄存器来说,这是有可能的。写入将覆盖任何已发生的变化,这是否会成为问题取决于具体情形。举例来说,一个32位寄存器,最高16位用于标志,最低16位用于计数。硬件对标志位的变化可能因此而丢失。
最简单的修复办法是从函数参数中移除 volatile 限定符。如果这不可行,另一种简单的修复就是:
uint16_t tmp = pb->bar;
tmp++;
bp->bar = tmp;
虽然有点丑,但对性能的影响并不显著。
另一种选择是偏离这条规则。在嵌入式系统中,存在许多情形,我们可能希望在同一表达式中读取一个 volatile 限定的变量并把它写入到另一个变量中。但由于MISRA不能保证这样做始终安全,他们因此制定了相应的禁止规则。