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 int
或 unsigned 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 兼容的——您甚至可以在不同的字节顺序架构之间移植它。它解决了上述所有问题。
我定义了一个枚举类型的位域来匹配嵌入式系统中的一组位。我正在尝试在 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 int
或 unsigned 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 兼容的——您甚至可以在不同的字节顺序架构之间移植它。它解决了上述所有问题。