使用有符号枚举值进行位运算

3

我正在使用枚举值来表示标志:

typedef enum
{
    a = 0x00,
    b = 0x01u, // the u has no influence, as expected
    c = 0x02u, // the u has no influence, as expected
...
} enum_name;

volatile unsigned char* reg = SomeAddress;
*reg |= b;

根据MISRA-C:2004标准,不应使用带符号类型进行按位操作。不幸的是,我的编译器IAR将有符号整数(或short或char)用作枚举的底层类型,我能找到的唯一选项只涉及大小,而不涉及有符号性(“--enum-is-int”)。

当然我可以进行强制类型转换,但我正在寻找一种更全面的方法。 - lkanab
1
“全面方法”是什么意思?就像大多数MISRA规则一样,它们告诉您要避免完全正确的代码。这是完全正确的代码。为了符合规定,您将不得不使用冗余转换使代码变劣。 - M.M
1
首选选项是:volatile unsigned char *reg =(volatile unsigned char *)SomeAddress; - M.M
MISRA 可能是许多胡言乱语,但在某些行业中它是事实上的标准。你可能是对的,但祝你好运。 - lkanab
2
您是否需要履行MISRA-C合规矩阵?如果是这样,我看不出当前接受的答案如何有所帮助。在我看来,您正在混淆MISRA-C的底层类型概念和编译器的实现。底层类型是MISRA用来警告开发人员整数提升的微妙和有时危险影响的抽象。在这种情况下,正如Serge所提到的,有符号操作数依赖于位运算的实现特定行为。通常需要记录偏差记录以记录对此的认识。 - Veriloud
显示剩余2条评论
2个回答

2
无论底层类型是有符号还是无符号,只要您仅使用正值作为枚举值,就没有关系,因为正值应具有与有符号或无符号类型相同的表示。标准在6.2.6.2类型表示/整数类型§5中说:带有零号位的有效(非trap)有符号整数类型对象表示是相应无符号类型的有效对象表示,并且应表示相同的值。 所以如果需要,您可以安全地将其转换为无符号类型。无论如何,如果底层类型是char(或者是unsigned char),在任何计算之前都可以(悄悄地)提升为int。
我认为MISRA-C:2004规定不应使用带符号类型进行位运算,因为标准明确指出负数的表示是实现定义的:
对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要填充位;应该恰好有一个符号位...如果符号位为1,则应按以下方式修改值:
- 具有符号位0的相应值被取反(符号和大小); - 符号位的值为-(2N)(二进制补码); - 符号位的值为-(2N-1)(一的补码)。 - 应用哪个是实现定义的
TL/DR:如果没有警告(对于位或运算符|,您不应该有警告),则可以完全不使用转换。如果将正值转换为无符号类型,则表示不变,因此,如果您(或您的公司规定)选择遵循MISRA-C,也可以安全地将其转换为无符号类型。

当您使用补码时,符号确实很重要: *ret &= ~b; (这正是IAR的CSTAT所认为的)。 - lkanab
对于非二进制补码机器,这是可以想象的,会出现问题:~b 具有正确的反转位模式,但是在有符号类型中。如果 *ret 具有无符号类型,则从有符号到无符号的转换会更改位模式。 - Potatoswatter
据我所知,枚举类型的底层类型无论如何都是 int。你可以通过添加一个枚举值 force_unsigned = -1u 来强制将底层类型设置为 unsigned。但是,其他的枚举值仍然是 int 类型的,实际上在表达式中命名 force_unsigned 会导致整数溢出。(不过这个技巧在 C++ 中可行。) - Potatoswatter
@lkanab 对于 ~ 运算符,符号并不重要。更确切地说,标准规定:如果提升后的类型是无符号类型,则表达式 ~E 等效于该类型中可表示的最大值减去 E。这保证了如果 n 是正数,则 ~((signed) n)~((unsigned) n) 具有相同的表示形式。因为在转换负值时:如果新类型是无符号的,则通过反复添加或减去新类型中可以表示的最大值加一,直到该值在新类型的范围内 - Serge Ballesta
@Serge,没错,MISRA-C指南是关于警告这是实现定义的行为。它并没有“禁止”任何东西(正如其他人在上面的评论中提到的)。在符合MISRA-C开发的过程中,您必须通过偏差记录记录您意识到依赖于C抽象机器未明确定义的行为,并且MISRA工具应该允许此操作。 - Veriloud

2
根据IAR C/C++ Development Guide for ARM的第169页和211页,如果启用IAR语言扩展(-e命令行选项或IDE中的Project>Options>C/C++ Compiler>Language>Allow IAR extensions),则可以定义您的enum类型。
特别地,您应该定义一个额外的“哨兵”值,以确保编译器选择正确的类型。它更喜欢有符号类型,并使用最小可能的整数类型,因此哨兵应该是相应无符号整数类型可以描述的最大正整数。例如,
typedef enum {
    /* ... */
    enum_u8_sentinel = 255U
} enum_u8;

typedef enum {
    /* ... */
    enum_u16_sentinel = 65535U
} enum_u16;

typedef enum {
    /* ... */
    enum_u32_sentinel = 4294967295UL
} enum_u32;

虽然这是正确的,但这是一种特定于实现的语言扩展,不可移植,并且本身违反了MISRA-C:2004规则1.1而没有偏差。这相当于MISRA C:2012规则1.2。 - Andrew
我没有MISRA规范的访问权限,所以我会相信你的话。当然,便携式解决方案是使用预处理器宏。 - Nominal Animal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接