Starting with the 1999 edition, the ISO C standard defines a standard header <stdint.h>
which defines, among other things, the typedefs intmax_t
and uintmax_t
. These designate, respectively, "a (signed|unsigned) integer type capable of representing any value of any (signed|unsigned) integer type".
For example, if, as is typical, the widest signed and unsigned integer types are long long int
and unsigned long long int
, both of which are typically 64 bits, then intmax_t
and uintmax_t
might be defined in <stdint.h>
as follows:
typedef long long int intmax_t;
typedef unsigned long long int uintmax_t;
There is a limited set of predefined signed and unsigned integer types, ranging from signed
, unsigned
, and plain char
up to signed
and unsigned
long long int
.
C99 and C11 also permit implementations to define extended integer types, which are distinct from any of the standard types and have names that are implementation-defined keywords.
Both gcc and clang, on some but not all targets, support types __int128
and unsigned __int128
. These act like 128-bit integer types, but they are not treated as extended integer types, and the documentation for both compilers states that they do not support any extended integer types. Because these are not integer types as the Standard defines the term, the typedefs intmax_t
and uintmax_t
are for 64-bit types, not 128-bit types.
None of this violates the C standard (implementations are not required to have any extended integer types, and they're permitted to have arbitrary extensions as long as they don't break any strictly conforming programs). But it seems to me that it would make perfect sense for __int128
and unsigned __int128
to be treated as extended integer types, and for intmax_t
and uintmax_t
to be 128-bit types.
The rationale for not doing this is that changing the size of intmax_t
and uintmax_t
would be "an ABI-incompatible change".
The Clang C++ status page says, in footnote (5):
No compiler changes are required for an implementation such as Clang that does not provide any extended integer types.
__int128
is not treated as an extended integer type, because changingintmax_t
would be an ABI-incompatible change.
(Yes, this primarily discusses C++, but the rules are the same as for C.)
In a gcc bug report, the claim is made that:
sizeof(intmax_t)
is fixed by various LP64 ABIs and cannot be changed
In both cases, no reference is given for this claim.
An x86_64 ABI document titled "System V Application Binary Interface, AMD64 Architecture Processor Supplement, Draft Version 0.99.6" does not mention intmax_t
or uintmax_t
, or even the <stdint.h>
header. It does specify sizes and alignments for the predefined integer types (in Figure 3.1).
Finally, my question: Is the claim that the sizes of intmax_t
and uintmax_t
restricted by an ABI valid? If so, what ABI imposes such a requirement? (And, incidentally, why?)
(In my opinion, such a requirement, if it exists, is unwise. It defeats the purpose of the C standard's permission to define extended integer types, and the intended meaning of intmax_t
and uintmax_t
. It makes it much more difficult to use 128-bit integer types effectively on systems that support them, while falling back to narrower types on other systems.)
Update: In N2303, titled "intmax t, a way out", Jens Gustedt proposes tweaking the definitions of [u]intmax_t
to permit adding extended integer types wider than long long
without having to update [u]intmax_t
. For example, intmax_t
might be a typedef for long long
, but the implementation could still provide, say, __int128
as an extended integer type.
References:
As Colonel Thirty Two notes, a compiler unilaterally making this change would break calls between compilation units that pass uintmax_t
parameters or return uintmax_t
values. Even though the SysV ABI doesn't define how these types are passed, as a matter of practicality maintaining their definitions is part of conforming to the platform ABI.
Even if it weren't for this ABI issue, a compiler still couldn't unilaterally make this change, because it would require matching changes to every targeted platform's C standard library. Specifically, it would at least require updates to the printf
and scanf
function family, imaxabs
, imaxdiv
, and strtoimax
and strtoumax
and their variants.
Changing types like intmax_t
and uintmax_t
also changes the ABI of all the programs that use them, as they now refer to different types.
Say you have program A that uses a function in shared library B with a uintmax_t
parameter. If GCC changes the definition of uintmax_t
and A (but not B) gets recompiled, then uintmax_t
in A and uintmax_t
in B now refer to two different types, breaking the ABI.
I think the key to understand here is that just because something is not documented in an ABI specification does not mean it is not part of an ABI. As soon as a type is used across a library boundry then it's properties become part of the ABI of that library.
By defining (u)intmax_t in a standard header and using them in functions of the standard library they become part of the ABI of that library whether they are included in any formal ABI specification or not.
This is especially an issue for Unix-like platforms where the C standard library is treated as part of the platform, not part of the compiler.
Now it would be possible to transition this. Printf uses macros for type specifiers, so those macros could be defined differently depending on the size of intmax_t. Macros could similarly be used to map the handful of functions in the standard library to different implementations but it's a bunch of extra work for questionable gains, so it's hardly surprising that gcc took the path of least resistance to adding the functionality they needed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With