-10

I have defined a bitfield of enum types to match a set of bits in an embedded system. I'm trying to write a test harness in MSVC for the code, but comparing what should be equal values fails.

The definition looks like this:

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;

Later the following comparison fails:

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

I examined the Mode bool in the debugger, and it looked odd. The value of Mode is -1.

It's as if MSVC considers the value to be a two's complement number, but 1 bit wide, so 0b1 is decimal -1. The enum sets PARALLEL to 1, so the two do not match.

The comparison works fine on the embedded side using LLVM or GCC.

Which behavior is correct? I assume GCC and LLVM have better support for the C standards than MSVC in areas such as bit fields. More importantly, can I work around this difference without making major changes to the embedded code?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    Bit-fields are non-portable so speculating about standard compliance isn't very meaningful. – Lundin May 28 '19 at 14:18
  • @Lundin Ah yes, you are correct. Hmm, I may be screwed then... Or maybe I can use some preprocssor hackery to deal with it. –  May 28 '19 at 14:21
  • 2
    @Lundin Or more to the point, the integer type used for an enumeration is implementation defined. – dbush May 28 '19 at 14:24
  • @dbush of course, I'm asking if there is a way to make it work with both LLVM and MSVC. Best I can think of is to #ifdef _WIN32 –  May 28 '19 at 14:28
  • @dbush The point (top) of the iceberg perhaps :) There's multiple problems here, enums is just one. I wrote a detailed answer listing all portability issues I could spot. – Lundin May 29 '19 at 07:57
  • @Hulk if I find time I'll get around to replacing it with dummy code. Better still delete the question. –  Jul 20 '20 at 14:02

4 Answers4

4

Dissecting this in detail, you have the following problems:

  • There is no guarantee that Type : 1 is the MSB or LSB. Generally, there are no guarantees of the bit-field layout in memory at all.

  • As mentioned in other answers, enumeration variables (unlike enumeration constants) have implementation-defined size. Meaning that you can't know their size, portably. In addition, if the size is something which isn't the same as either int or _Bool, the compiler need not support it at all.

  • Enums are most often a signed integer type. And when you create a bit-field of size 1 with a signed type, nobody including the standard knows what it means. Is it the sign bit you intend to store there or is it data?

  • The size of what the C standard calls "storage unit" inside the bit-field, is unspecified. Typically it is alignment-based. The C standard does guarantee that if you have several bit-fields of the same type trailing each other, they must be merged into the same storage unit (if there is room). For different types, there are no such guarantees.

    It is fairly common that when you go from one type like POSITION_e to a different type bool, the compiler places them in different storage units. In practice meaning that there's a high risk of padding bit insertion whenever this happens. Lots of mainstream compilers do in fact behave just like that.

  • In addition, a struct or union may contain padding bytes anywhere.

  • In addition, there is the endianess problem.

Conclusion: bit-fields cannot be used in programs that need any form of portability. They cannot be used for the purpose of memory mapping.

Also, you really don't need all these abstraction layers - it's a simple dip-switch, not a space shuttle! :)


Solution:

I would strongly recommend to drop all of this in favour for a plain uint32_t. You can mask individual bits with plain integer constants:

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

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

This is massively portable, well-defined, standardized, MISRA-C compliant - you can even port it between different endianess architectures. It solves all of the above mentioned problems.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

I would use the following approach.

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

Then set the value and test the value as follows.

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

The value of 1 will have the least significant bit turned on and the value of 0 would have the least significant bit turned off.

And this should be portable across multiple compilers.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
  • The only issue is that it does require modifying the code in every place where the comparison takes place, where as my solution only requires modifying the definition. –  May 29 '19 at 07:52
0

The exact type used to represent an enum is implementation defined. So what is most likely happening is that MSVC is using char for this particular enum which is signed. So declaring a 1-bit bitfield of this type means you get 0 and -1 for the values.

Rather that declaring the bitfield as the type of the enum, declare them as unsigned int or unsigned char so the values are properly represented.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • I was considering defining as unsigned or bool. The only down side, which I would like to avoid, is that you then don't get compiler warnings if you assign some value other than one of the enumerated ones. –  May 28 '19 at 14:39
0

A simple fix I came up with, which is only valid for MSVC and GCC/LLVM, is:

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