MSVC 1 位枚举类型等于 -1 且相等性测试失败

MSVC 1 bit enum type equals -1 and equality test fails

我定义了一个枚举类型的位域来匹配嵌入式系统中的一组位。我正在尝试在 MSVC 中为代码编写测试工具,但比较应该相等的值失败。

定义如下所示:

typedef enum { SERIAL, PARALLEL } MODE_e;
typedef union {
    struct {
        TYPE_e      Type    : 1;    // 1
        POSITION_e  1Pos    : 1;    // 2
        POSITION_e  2Pos    : 1;    // 3
        bool        Enable  : 1;    // 4
        NET_e       Net     : 1;    // 5
        TYPE_e      Type    : 1;    // 6
        bool        En      : 1;    // 7
        TIME_e      Time    : 3;    // 8-10
        MODE_e      Mode    : 1;    // 11
        bool        TestEn  : 1;    // 12
        bool        DelayEn : 1;    // 13
        MODE_e      Mode    : 1;    // 14
        bool        xEn     : 1;    // 15
        MODE_e      yMode   : 1;    // 16
        bool        zEnable : 1;    // 17
    } Bits;
    uint32_t Word;
} BITS_t;

后面比较失败:

Store.Bits.Mode = PARALLEL;
if (store.Bits.Mode == PARALLEL)
    ...

我检查了调试器中的 Mode 布尔值,它看起来很奇怪。 Mode的值为-1。

就好像 MSVC 认为该值是一个 2 的补码,但是 1 位宽,所以 0b1 是十进制 -1。枚举将 PARALLEL 设置为 1,因此两者不匹配。

在使用 LLVM 或 GCC 的嵌入式端,比较工作正常。

哪种行为是正确的?我假设 GCC 和 LLVM 在位字段等领域比 MSVC 对 C 标准的支持更好。更重要的是,我可以在不对嵌入式代码进行重大更改的情况下解决这个差异吗?

用于表示 enum 的确切类型是实现定义的。因此,最有可能发生的是 MSVC 正在使用 char 作为这个已签名的特定 enum。因此,声明这种类型的 1 位位域意味着您将获得 0 和 -1 的值。

与其将位域声明为 enum 的类型,不如将它们声明为 unsigned intunsigned char,以便正确表示值。

我提出的一个简单的修复方法,仅对 MSVC 和 GCC/LLVM 有效,是:

#ifdef  _WIN32
#define JOFF        0
#define JON         -1
#else
#define JOFF        0
#define JON         1
#endif

typedef enum { SERIAL = JOFF, PARALLEL = JON } TEST_MODE_e;

我会使用以下方法。

typedef enum { SERIAL_TEST_MODE = 0, PARALLEL_TEST_MODE = 1 } TEST_MODE_e;

然后设置值,测试值如下。

config.jumpers.Bits.TestMode = PARALLEL_TEST_MODE;
if (config.jumpers.Bits.TestMode & PARALLEL_TEST_MODE)
    ...

值 1 将打开最低有效位,值 0 将关闭最低有效位。

这应该可以跨多个编译器移植。

详细剖析,您遇到以下问题:

  • 无法保证 Type : 1 是 MSB 或 LSB。通常,根本无法保证内存中的 bit-field 布局。

  • 如其他答案中所述,枚举变量(与枚举常量不同)的大小为 implementation-defined。这意味着您无法便携地知道它们的大小。此外,如果大小与 int_Bool 不同,编译器根本不需要支持它。

  • 枚举通常是有符号整数类型。当您创建大小为 1 的带符号类型的 bit-field 时,包括标准在内的任何人都不知道这意味着什么。它是您打算存储在那里的符号位还是数据?

  • bit-field 中 C 标准称为“存储单元”的大小未指定。通常为 alignment-based。 C 标准确实保证如果你有几个相同类型的 bit-field 彼此尾随,它们必须合并到同一个存储单元中(如果有空间)。对于不同的类型,没有这样的保证。

    当您从一种类型 POSITION_e 转换为另一种类型 bool 时,编译器将它们放在不同的存储单元中是很常见的。实际上,这意味着每当发生这种情况时,填充位插入的风险就很高。事实上,许多主流编译器的行为都是如此。

  • 此外,结构或联合可以在任何地方包含填充字节。

  • 另外还有字节序问题

结论:bit-fields 不能用于需要任何形式的可移植性的程序中。它们不能用于内存映射。

此外,您真的不需要所有这些抽象层 - 这是一个简单的 dip-switch,而不是 space 穿梭机! :)


解决方案:

我强烈建议放弃所有这些以支持简单的 uint32_t。您可以使用纯整数常量屏蔽单个位:

#define DIP_TYPE (1u << 31)
#define DIP_POS   (1u << 30)
...

uint32_t dipswitch = ...;
bool actuator_active = dipswitch & DIP_TYPE; // read
dipswitch |= DIP_POS;     // write

这是大规模可移植的、well-defined、标准化的、MISRA-C 兼容的——您甚至可以在不同的字节顺序架构之间移植它。它解决了上述所有问题。