【问题标题】:Unpermitted operand to operator '++' [MISRA 2012 Rule 10.1, required]运算符 '++' 的未经允许的操作数 [MISRA 2012 规则 10.1,必需]
【发布时间】:2019-09-10 10:21:47
【问题描述】:

我正在尝试修复其他人编写的模块的 Misra 警告。我观察到++ 操作正在enum 上使用。

我提到了SE question,它就同一主题进行了讨论。如何解决此错误?我需要建议模块所有者来更改实现吗?

#include <stdio.h>

typedef enum
{  
    COMPARE = 0,
    INCONSISTENT = 10,
    WRITE,
    READ,
    FINISHED  
}TestsType;

static TestsType CurrentTest;

void fun1(void)
{

    if(READ != CurrentTest)
    {

            CurrentTest++;
    }
    else
    {
            CurrentTest = FINISHED;
    }
}

int main(void) {
    // your code goes here

    CurrentTest = COMPARE;
    fun1();
    printf("%d", CurrentTest);

    return 0;
}

我特意在代码中保留了这样的enum,以了解任何影响。但是,在实际代码中,如下所示。

typedef enum
{  
        COMPARE,
        INCONSISTENT,
        WRITE,
        READ,
        FINISHED  
}TestsType;

【问题讨论】:

  • 在发布的代码中CurrentTest++ 将产生一个值1,它不是定义的enum 值之一。相反,当READ == CurrentTest 不需要if .. else
  • @WeatherVane 我不确定开发人员对此 if 条件的意图。但是在实际代码中,枚举值是按顺序排列的,不会在 0 之后跳转到 10。
  • @kapilddit 有为枚举自定义整数值的正当理由,它用于“实际代码”
  • 我需要做些什么来避免这个警告而不影响代码?
  • 如果您需要使用 ++,那么您可能不应该选择枚举,而是选择普通整数类型和 #defined 常量。如果您知道这些值是连续的,那么它并没有真正的危害,但它不符合 MISRA。

标签: c enums misra pc-lint


【解决方案1】:

增加枚举是错误的!

枚举被添加到语言中,作为 #define 的一个更好的替代方法,用于许多常量,并且在其他方​​面被认为是整数(即整数的 const 数组)。执行更多操作需要运行时检查。

由于枚举值不必是连续的,当它们被视为整数时,递增它们是没有意义的。如果编译器确实允许它,它认为它正在递增一个 int,这可能意味着您的值不对应于之后枚举中的任何值。

所以我的建议是“不要这样做”,即使特定的编译器允许你这样做。将其重写为明确的内容。

如果您想在由连续整数表示的特定状态范围内循环,则可以使用枚举,但前提是您必须使其值也连续。对解释不修补的定义放了很多警告。然后递增一个表示状态的 int,然后可以安全地与枚举进行比较。

【讨论】:

    【解决方案2】:

    使用像 MISRA 这样的标准的全部意义在于避免有风险的代码。毫无疑问,递增枚举是有风险的。

    如果你有一些增加枚举的代码,并且它运行良好(在所有条件下),这只是因为可能没有全部写下来的一些相互关联的假设和约定对于后来的维护程序员来说,这几乎肯定不会是显而易见的(并受到尊重)。

    因此,确实,没有简单的解决方法。任何简单的修复(可能会让您的 MISRA 检查器关闭)都可能使实践中的固有风险保持不变——也就是说,您可能满足 MISRA 的字面意思,但不满足精神(这显然是倒退的)。

    所以是的,您应该要求(不仅仅是建议)模块所有者更改实现。

    修改后的实现会是什么样子?我认为应该有以下一个或多个方面:

    1. 使用int 和一些#defined 常量。
    2. 有一个单独的封装函数来从一种状态映射到另一种状态。
    3. 使用显式转换表将一种状态映射到下一种状态。
    4. 如果存在大量状态,并且大多数状态按顺序排列,这样 +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 ... ;
            }
        }
    

    使用switch 的优点是,如果您在这样的开关中留下枚举值,现代编译器会警告您。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多