C Enum Sizes; or, How MSVC Ignores The Standard Once Again

7 points by ettolrach


olliej

This is absolutely unreasonable. The windows ABI has a bunch of choices that I'm sure that people at MS wish were different, but that's the platform ABI: it cannot change.

Pointing the modern C standard is unreasonable: If the standard specified behavior that is incompatible with an existing ABI the spec is what is wrong. Like most ABI dependent things this should have been labeled implementation defined.

And it was.

But I want to be even more clear. The first standardized version of C was C89, according to wikipedia MS was at version 6 of their C compiler, and already had a well established market of existing software that required ABI stability. Here is what C89, the standard that you should be applying said.

First, enumeration values:

The expression that defines the value of an enumeration constant shall be an integral constant expression that has a value representable as an int.

Nothing about growing the type, literally the value must fit in an int. Also it says int not unsigned or unsigned int, so the oft-maligned use of signed values for enums was correct.

It then goes on to state:

Each enumerated type shall her compatible with an integer type. the choice of type is implementation-defined

Again, nothing about growing the size, and explicitly deferring the choice (and thus size) of the type to the implementation.

So in summary the specification said:

MS made the perfectly reasonable choice, as an implementation, to use a signed int to contain a value explicitly stated to be constrained to the bounds of a signed int.

Then someone said: let's change the specification so that the type grows, and so breaks existing code on a frozen ABI. That's a spec bug, not an implementation error.

As far as "if a value outside of the range of an integer isn't permitted then such enums shouldn't have happened", which is a nice thought but consider how much more lenient compilers were back then.

What I suspect happened -- many times over -- is this:

enum MyEnum {
  // ...
  Something = 0xffffffff // -1, as the enum is signed so this fits in an 32-bit int
};
struct MyStruct {
  MyEnum e;
  int myotherdata;
}; // 8 bytes
// ...
MyStruct *s = ...;
size_t count = ...;
fwrite(s, sizeof(MyStruct), count, someFile);

Now lets consider what happens if MSVC matches the spec, and expanding to a 64bit value is what has to happen:

enum MyEnum {
  // ...
  Something = 0xffffffff // 42.... in a signed 64bit value
};
struct MyStruct {
  MyEnum e;
  int myotherdata;
}; // 16 bytes
MyStruct *s = ...;
size_t count = ...;
fread(s, sizeof(MyStruct), count, someFile);

This is now broken.