【问题标题】:MSVC 1 bit enum type equals -1 and equality test failsMSVC 1 位枚举类型等于 -1 并且相等性测试失败
【发布时间】:2019-05-28 14:13:08
【问题描述】:

我已经定义了一个枚举类型的位域来匹配嵌入式系统中的一组位。我正在尝试在 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 在位域等领域对 C 标准的支持比 MSVC 更好。更重要的是,我能否在不对嵌入式代码进行重大更改的情况下解决这种差异?

【问题讨论】:

  • 位域是不可移植的,因此推测标准合规性没有多大意义。
  • @Lundin 是的,你是对的。嗯,那我可能会被搞砸……或者我可以使用一些预处理程序来处理它。
  • @Lundin 或者更重要的是,用于枚举的整数类型是实现定义的。
  • @dbush 当然,我在问是否有办法让它同时适用于 LLVM 和 MSVC。我能想到的最好的方法是#ifdef _WIN32
  • @dbush 也许是冰山一角:) 这里有多个问题,枚举只是其中之一。我写了一个详细的答案,列出了我能发现的所有可移植性问题。

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


【解决方案1】:

详细剖析这个,你有以下问题:

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

  • 如其他答案中所述,枚举变量(与枚举常量不同)具有实现定义的大小。这意味着你无法知道它们的大小,可移植。此外,如果大小与int_Bool 不同,编译器根本不需要支持。

  • 枚举通常是有符号整数类型。当你用有符号类型创建一个大小为 1 的位域时,包括标准在内的任何人都不知道它的含义。是您打算存储在那里的符号位还是数据?

  • 位域内 C 标准称为“存储单元”的大小未指定。通常它是基于对齐的。 C 标准确实保证,如果您有多个相同类型的位域彼此尾随,则它们必须合并到同一个存储单元中(如果有空间)。对于不同的类型,没有这样的保证。

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

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

  • 另外还有字节序问题。

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

另外,你真的不需要所有这些抽象层——它只是一个简单的拨码开关,而不是航天飞机! :)


解决方案:

我强烈建议放弃所有这些,转而使用简单的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

这是高度可移植的、定义明确的、标准化的、符合 MISRA-C 的 - 您甚至可以在不同的字节序架构之间移植它。它解决了上述所有问题。

【讨论】:

    【解决方案2】:

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

    #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;
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 我正在考虑将其定义为无符号或布尔值。我想避免的唯一缺点是,如果您分配的值不是枚举值之一,则不会收到编译器警告。
      【解决方案4】:

      我会使用以下方法。

      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 将关闭最低有效位。

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

      【讨论】:

      • 唯一的问题是它确实需要在进行比较的每个地方修改代码,而我的解决方案只需要修改定义。
      猜你喜欢
      • 2011-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-27
      • 1970-01-01
      • 2017-03-11
      • 2023-03-06
      • 2011-01-15
      相关资源
      最近更新 更多