使用像 MISRA 这样的标准的全部意义在于避免有风险的代码。毫无疑问,增加枚举是有风险的。
如果您有一些增加枚举的代码,并且它运行良好(在所有条件下),那只是因为许多互锁的假设和约定可能没有全部写下来,而且几乎肯定不会很明显(并获得荣誉)后来的维护程序员。
因此,确实,没有简单的解决方法。任何简单的修复(可能会让您的 MISRA 检查器关闭)都可能使实践中的固有风险保持不变——也就是说,您可能满足 MISRA 的文字,但不满足精神(这显然是倒退的)。
所以是的,您应该要求(不仅仅是建议)模块所有者更改实现。
修改后的实施会是什么样子?我认为应该有以下一个或多个方面:
- 使用 an
int
和一些#define
d 常量。
- 有一个单独的封装函数来从一个状态映射到下一个状态。
- 使用显式转换表将一种状态映射到下一种状态。
- 如果有大量状态,并且它们中的大多数是按顺序排列的,这样 +1 增量就可以很好地封装它(比一堆任意状态转换更清晰可靠),继续使用 +1 增量,但附带一些断言以确保各种假设成立。例如:
enum state {
OFF = 0,
LOW = 3,
MEDIUM,
HIGH,
EXCEPTIONAL = 10
};
/* States LOW..HIGH are assumed to be contiguous. Make sure you keep them so! */
/* If (and only if) you add or subtract states to the contiguous list, */
/* make sure to also update N_CONTIGUOUS_STATES. */
#define N_CONTIGUOUS_STATES 3
enum state nextstate(enum state oldstate)
{
/* Normally performing arithmetic on enums is wrong. */
/* We're doing so here in a careful, controlled, constrained way, */
/* limited just to the values LOW..HIGH which we're calling "contiguous". */
assert((int)LOW + N_CONTIGUOUS_STATES - 1 == (int)HIGH);
if(oldstate >= LOW && oldstate < HIGH) {
return (enum state)((int)oldstate + 1);
} else {
/* perform arbitrary mappings between other states */
}
}
这里的目的是记录正在发生的事情,并确保如果以后的维护程序员以任何方式更改枚举定义,从而打破允许直接递增的某些连续状态的假设,则断言将失败。
...但我赶紧补充说,这不是一个完整的解决方案。要保留的更重要的保证是处理每个状态转换,如果以后的维护程序员添加新状态但忘记更新转换映射,则更容易违反这一点。让编译器帮助您保证这一点的一种好方法是使用switch
语句,尽管这几乎会迫使您使每个转换都显式(即,不使用 +1 快捷方式):
enum state nextstate(enum state oldstate)
{
switch(oldstate) {
case OFF: return ... ;
case LOW: return MEDIUM;
case MEDIUM: return HIGH;
case HIGH: return ... ;
case EXCEPTIONAL: return ... ;
}
}
使用 a 的优点switch
是,如果您将枚举值留在这样的开关之外,现代编译器会警告您。