如何在不违反 MISRA 规则的情况下将位域变量分配给 uint8_t 变量?

How to assign bitfield variable to uint8_t variable without violating MISRA rules?

我有一个 typedef struct 命名的角色。

typedef struct {
    unsigned int a : 1;
    unsigned int b : 1;
    unsigned int c : 1;
    unsigned int d : 1;
    unsigned int o : 1;
    unsigned int p : 1;
    unsigned int q : 1;
    unsigned int x : 1;
} Character;

static Character tempChar;

void writeVar(const uint8_t *pData)
{
    tempChar.a = pData[0] >> 5;
    ...
}

当我尝试将一个 uin8_t 变量(值为 01)分配给这些位域之一时,我违反了 MISRA 规则 10.6,该规则规定:

The value of a composite expression shall not be assigned to an object with wider essential type

有没有办法在不违反 MISRA C 的情况下将位域分配给 uint8_t?

出于这个原因,我发现 MISRA C 过于复杂。 无论如何,您没有说要直接分配它。如果是这种情况,您可以采取以下措施:

typedef union {
    
    struct {
        unsigned int a : 1;
        unsigned int b : 1;
        unsigned int c : 1;
        unsigned int d : 1;
        unsigned int o : 1;
        unsigned int p : 1;
        unsigned int q : 1;
        unsigned int x : 1;
    };
    
    uint8_t u8Value;
    
} Character;

并通过访问 tempChar.u8Value 而不是位字段来设置这些值。例如,

tempChar.u8Value |= (1 << 0);

会将 tempChar.a 设置为 1

那仍然会保持代码的整洁度(可读性)到同样的程度。例如,

if(1 == tempChar.a)
{ 
    // Some code
}

如果需要,表达式 pData[0] >> 5 中的两个操作数将是 promotedintpData[0] 会发生)。

表达式的结果是 int.

提升和从 intunsigned int 的转换,虽然在正常情况下完全有效且很好,但足以让非常严格的 MISRA 抱怨。

简单的解决方案(如评论中所示)是使用转换将 pData[0] 显式转换为 unsigned int

tempChar.a = pData[0] >> 5;

在这个5中是一个带符号的整型常量。您应该使用 5U 作为无符号常量

此外,右移操作的结果将是一个 int 所以你需要将结果类型转换回 unsigned int

tempChar.a = (unsigned int) (pData[0] >> 5U);

这里的核心问题与 MISRA 无关,而是与试图将值存储在位域中的特定槽有关。你无法知道你的位域布局实际上是如何在内存中结束的,因为那在 C 标准中没有定义。

你的位域是在MS字节还是LS字节中分配8个值位?它是否符合字节顺序?位序是什么?没人知道。第 1 步是摆脱位域。

第 2 步是摆脱任何东西 unsigned int 并使用 uint16_t/uint32_t


具体到 MISRA-C 10.6,禁止隐式转换为更广泛类型的规则总是被误导。 MISRA 用于此规则的基本原理是防止人们编写像 uint32_t u32 = u16a + u16b; 这样的代码,并认为 =u32 操作数不知何故神奇地意味着该操作将在 32 位上执行16的。但是在8/16位系统上,它是用16位算法进行的,可能会有overflows/wrap-around。

碰巧,对有符号类型进行位移 总是 一个非常糟糕的主意。 pData[0] 隐式提升为已签名的 int。还有其他 MISRA 规则处理此问题,而不是您引用的规则。

不管 MISRA 是什么,你都应该养成总是对无符号类型进行转换的习惯。 "It's not dangerous in this case" 是一个凄凉的理由。这意味着总是写 (uint32_t)pData[0] >> 5 并且应该在 之前应用强制转换 而不是在它之后。这消除了所有关于未定义行为左移和潜在算术右移等的不确定性。让优化器从那里担心操作数的实际使用大小。