ettolrach
(she/her)ettolrach
(she/her)Written by ettolrach, 2026-02-23.
My friend ran into an interesting bug, and then I ran into a very similar issue myself. I was surprised to not find many references to it online, so I decided to make a little post myself so I can reference it later.
In C we can declare an enum like so:
enum Composer { Beethoven = 1, Tchaikovsky = 2, VaughanWilliams, };
C calls variants enumerators. We can give our enumerators an
enumeration constant using the = character. If we
don't give the first enumerator (that's Beethoven above) an
enumeration constant, then its constant will be 0. All subsequent
enumerators without declared constants will have a constant that's 1
greater than the previous enumerator. So, VaughanWilliams
is guaranteed to have a constant of 3.
But what's the type of these numbers? Well, we could be explicit and declare it (this is a new feature in C23, so a lot of websites are wrong and claim that it's impossible to specify a type):
enum Composer: int { Beethoven = 1, Tchaikovsky = 2, VaughanWilliams, };
And the C standard provides this answer for when we don't specify a type (§ 6.7.3.3, 2):
All enumerations have an underlying type. The underlying type can be explicitly specified using an enum type specifier and is its fixed underlying type. If it is not explicitly specified, the underlying type is the enumeration’s compatible type, which is either
charor a standard or extended signed or unsigned integer type.
So, a standard-compliant compiler can choose our
Composers enum to have an underlying type of
char or long long, both are compliant
decisions.
GCC chooses 32 bits and correctly expands to 64 when an enumerator's
constant is set to a value larger than 232 - 1. Clang does
the same. We can confirm this with this code where we set
VaughanWilliams to 232, a value not
representable using an int:
#include <stdio.h> enum Composer { Beethoven = 1, Tchaikovsky = 2, VaughanWilliams = 4294967296, }; int main() { enum Composer comp = VaughanWilliams; printf("Vaughan Williams: %lu\n", comp); printf("size: %lu\n", 8*sizeof(enum Composer)); } // Output: // Vaughan Williams: 4294967296 // size: 64
And what happens when we run this code on MSVC?
// Output: // Vaughan Williams: 0 // size: 32
Oh no. That is code running on Visual Studio 2026 18.3.1, MSVC 14.50, the latest at the time of writing. MSVC also doesn't support the underlying type annotation, even after enabling the "experimental" ISO C23 support in the VS project options.
But hey. This couldn't cause a runtime error, right?
So to summarise, if you're writing a C program or library and want an
enum with constants larger than 232 - 1, if you're using
Clang or GCC, then great! If you're using MSVC, then you should just use
a long long (or an unsigned version if you don't need
negatives) and constants. You should probably consider making your
program more type-safe by using structs which wrap values anyway. If
you're using a C library (which is pre-compiled, so a static or shared
library) and you know the library supports MSVC compilation, then you
can safely assume all enums are 32-bit. If it supports Linux, then it
likely compiles using Clang or GCC, in which case the enum will expand
its size correctly. In fact, if you're using Rust and interacting with a
C FFI like I was, then bindgen can figure
out the appropriate type in std::ffi::c_xxx (e.g.
c_int or c_ulong).
Please be respectful. Any comment which I find objectionable will be removed. By submitting a comment, you agree to the privacy policy.
No comments yet.