C Enum Sizes; or, How MSVC Ignores The Standard Once Again
7 points by ettolrach
7 points by ettolrach
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.
Explicit C enum sizes are a C23ism. MSVC support for C23 is minimal, clang isnt in great shape and even GCC has some gaps: https://en.cppreference.com/w/c/compiler_support/23.html
If you want something enum-like with a predicable size across compilers you have to she C++ enums or #define some constants with consistent names.
Oh yeah, specifying enum sizes explicitly is the solution - and is what should have been added rather than the fun "make changes in the constants potentially cause the size of an enum to silently change" ABI breaking approach WG16 took.
But the entire complaint in this post is "MSVC is violating the standard out of general ill will/incompetence", while at the same time completely ignoring the fact that MSVC implements the spec (for enums) as it was originally specified, while pretending that the language has not changed in a ABI breaking way.
There are plenty of reasons to complain about MSVC, but complaining about them following the standard, and then not adopting an ABI breaking change that was created instead of simply fixing the problem, is not one of them.
This seems like a backwards-compatible ABI change. Old versions of the standard required the value to fit in an int, new ones allow promoting enums to larger underlying types if the values don’t fit. Anything that targeted older C and had enum values that don’t fit in an int is UB. Was UTC truncating them? Were the constants promoted to the correct integer types impacting arithmetic promotion of other things and truncated later?
The correct thing to do would probably be: