Skip to content

enum, bit fields and Visual Studio

Examine the C++ code below. The output is really counterintuitive, at least on Visual Studio 8.0.

#include <iostream>

enum AnEnum
{
	E0,
	E1,
	E2
};

struct S
{
	AnEnum e0:2;
	AnEnum e1:2;
	AnEnum e2:2;

} s;

int main(int argc, char* argv[])
{
	using namespace std;

	s.e0 = E0;
	s.e1 = E1;
	s.e2 = E2;

	cout << E0   << ", " << E1   << ", " << E2   << endl;
	cout << s.e0 << ", " << s.e1 << ", " << s.e2 << endl;

	return 0;
}

prints:
0, 1, 2
0, 1, -2

It turns out that Visual Studio's 8.0 compiler represents bit fields of enum types, such as AnEnum e2:2 with a signed type, but E2 as unsigned. So in this case s.e2 == E2 will result in false!

Both the Visual C++ Team and the folks working with the C++ standard are aware of the issue.

Comments

Mark Henning writes

1 thumb

This is an example of Microsoft compilers being FAR too lax.
If you refer to the canonical C definition of bit fields the only valid declarations are int, unsigned int, and signed int. Microsoft allows a number of extensions, but there are major gotchas. In this case the gotcha is that Enum types are undefined as to whether they are signed or unsigned. The compiler has to choose how the bitfield values are implemented. Unless explicitly declared as unsigned int, the compiler interprets the most significant bit of the value as a sign bit and thus 0x10 is really -2 because the compiler reads two as the binary value and interprets the 1 in the 2's place as a negative sign. If you defined the bit fields as log2(size)+1 bits [3 in this case], you would not have this problem, though you would "waste" a bit for each enum bitfield.

The code below should provide an alternative, though it is no longer type-safe; you will have to decide if type-safe and wasted bit is better than non-typesafe tight packing.

#include <iostream>

enum AnEnum
{
E0,
E1,
E2
};

struct S
{
unsigned int e0:2;
unsigned int e1:2;
unsigned int e2:2;

} s;

int main(int argc, char* argv[])
{
using namespace std;

s.e0 = (unsigned int)E0;
s.e1 = (unsigned int)E1;
s.e2 = (unsigned int)E2;

cout << E0 << ", " << E1 << ", " << E2 << endl;
cout << s.e0 << ", " << s.e1 << ", " << s.e2 << endl;

return 0;
}

should print:
0, 1, 2
0, 1, 2

# 21 Oct 2010, 11:44

Leave a reply