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

时间:2019-05-28 14:13:09

标签: c visual-c++ bit-fields

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

定义如下:

typedef enum { SERIAL_TEST_MODE, PARALLEL_TEST_MODE } TEST_MODE_e;
typedef union {
    struct {
        ACTUATOR_TYPE_e     ActuatorType        : 1;    // 1
        NORMAL_POSITION_e   Damper1NormalPos    : 1;    // 2
        NORMAL_POSITION_e   Damper2NormalPos    : 1;    // 3
        bool                EnableDamper2       : 1;    // 4
        NETWORK_MODE_e      NetworkMode         : 1;    // 5
        FIRE_ZONE_TYPE_e    FireZoneType        : 1;    // 6
        bool                PeriodicTestEn      : 1;    // 7
        TEST_TIME_e         TestTime            : 3;    // 8-10
        TEST_MODE_e         TestMode            : 1;    // 11
        bool                TestAHUEn           : 1;    // 12
        bool                TestDelayEn         : 1;    // 13
        INPUT_1_MODE_e      Input1Mode          : 1;    // 14
        bool                NightModeAHUEn      : 1;    // 15
        ETR_ACT_MODE_e      ETRActMode          : 1;    // 16
        bool                DSTEnable           : 1;    // 17
    } Bits;
    uint32_t Word;
} DIP_SWITCHES_t;

以下比较失败:

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

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

screenshot of debugger showing value of TestMode

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

使用LLVM或GCC在嵌入式方面进行比较比较好。

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

4 个答案:

答案 0 :(得分:2)

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

  • 不能保证ActuatorType : 1是MSB或LSB。通常,根本无法保证内存中的位域布局。
  • 如其他答案中所述,枚举变量(与枚举常量不同)具有实现定义的大小。意味着您无法便携地知道它们的大小。另外,如果大小与int_Bool都不相同,则编译器完全不需要支持它。
  • 枚举通常是带符号的整数类型。而且,当您创建带符号类型的大小为1的位字段时,包括标准在内的任何人都不知道它的含义。您打算在此处存储符号位还是数据?
  • C标准在位字段中称为“存储单元”的大小未指定。通常,它是基于对齐的。 C标准确实保证,如果您有多个相同类型的位域彼此尾随,则必须将它们合并到同一存储单元中(如果有空间)。对于不同类型,没有这样的保证。

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

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

  • 此外,还有Endianess问题。

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

此外,您真的不需要所有这些抽象层-这是一个简单的DIP开关,而不是航天飞机! :)


解决方案:

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

#define DIP_ACTUATOR_TYPE (1u << 31)
#define DIP_DAMPER1_POS   (1u << 30)
...

uint32_t dipswitch = ...;
bool actuator_active = dipswitch & DIP_ACTUATOR_TYPE; // read
dipswitch |= DIP_DAMPER1_POS;     // write

这是可移植的,定义明确的,标准化的,符合MISRA-C的-您甚至可以将其移植到不同的字节序体系结构之间。它解决了上述所有问题。

答案 1 :(得分:1)

我想出的一个简单修复方法是:

仅对MSVC和GCC / LLVM有效
#ifdef  _WIN32
#define JOFF        0
#define JON         -1
#else
#define JOFF        0
#define JON         1
#endif

typedef enum { SERIAL_TEST_MODE = JOFF, PARALLEL_TEST_MODE = JON } TEST_MODE_e;

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

我将使用以下方法。

active

然后按如下所示设置值并测试该值。

gender

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

这应该可以在多个编译器之间移植。