C has all those small, bigger, and biggest integer types like int
and long
and all that. And you can look in the section on limits to see what the largest int is with INT_MAX
and so on.
How big are those types? That is, how many bytes do they take up? We could use sizeof
to get that answer.
But what if I wanted to go the other way? What if I needed a type that was exactly 32 bits (4 bytes) or at least 16 bits or somesuch?
How can we declare a type that’s a certain size?
The header <stdint.h>
gives us a way.
For both signed and unsigned integers, we can specify a type that is a certain number of bits, with some caveats, of course.
And there are three main classes of these types (in these examples, the N
would be replaced by a certain number of bits):
intN_t
)int_leastN_t
)int_fastN_t
)188How much faster is fast
? Definitely maybe some amount faster. Probably. The spec doesn’t say how much faster, just that they’ll be the fastest on this architecture. Most C compilers are pretty good, though, so you’ll probably only see this used in places where the most possible speed needs to be guaranteed (rather than just hoping the compiler is producing pretty-dang-fast code, which it is).
Finally, these unsigned number types have a leading u
to differentiate them.
For example, these types have the corresponding listed meaning:
int32_t w; // x is exactly 32 bits, signed
uint16_t x; // y is exactly 16 bits, unsigned
int_least8_t y; // y is at least 8 bits, signed
uint_fast64_t z; // z is the fastest representation at least 64 bits, unsigned
The following types are guaranteed to be defined:
int_least8_t uint_least8_t
int_least16_t uint_least16_t
int_least32_t uint_least32_t
int_least64_t uint_least64_t
int_fast8_t uint_fast8_t
int_fast16_t uint_fast16_t
int_fast32_t uint_fast32_t
int_fast64_t uint_fast64_t
There might be others of different widths, as well, but those are optional.
Hey! Where are the fixed types like int16_t
? Turns out those are entirely optional…unless certain conditions are met189. And if you have an average run-of-the-mill modern computer system, those conditions probably are met. And if they are, you’ll have these types:
int8_t uint8_t
int16_t uint16_t
int32_t uint32_t
int64_t uint64_t
Other variants with different widths might be defined, but they’re optional.
There’s a type you can use that holds the largest representable integers available on the system, both signed and unsigned:
intmax_t
uintmax_t
Use these types when you want to go as big as possible.
Obviously values from any other integer types of the same sign will fit in this type, necessarily.
If you have a constant that you want to have fit in a certain number of bits, you can use these macros to automatically append the proper suffix onto the number (e.g. 22L
or 3490ULL
).
(x) UINT8_C(x)
INT8_C(x) UINT16_C(x)
INT16_C(x) UINT32_C(x)
INT32_C(x) UINT64_C(x)
INT64_C(x) UINTMAX_C(x) INTMAX_C
Again, these work only with constant integer values.
For example, we can use one of these to assign constant values like so:
uint16_t x = UINT16_C(12);
intmax_t y = INTMAX_C(3490);
We also have some limits defined so you can get the maximum and minimum values for these types:
INT8_MAX INT8_MIN UINT8_MAX
INT16_MAX INT16_MIN UINT16_MAX
INT32_MAX INT32_MIN UINT32_MAX
INT64_MAX INT64_MIN UINT64_MAX
INT_LEAST8_MAX INT_LEAST8_MIN UINT_LEAST8_MAX
INT_LEAST16_MAX INT_LEAST16_MIN UINT_LEAST16_MAX
INT_LEAST32_MAX INT_LEAST32_MIN UINT_LEAST32_MAX
INT_LEAST64_MAX INT_LEAST64_MIN UINT_LEAST64_MAX
INT_FAST8_MAX INT_FAST8_MIN UINT_FAST8_MAX
INT_FAST16_MAX INT_FAST16_MIN UINT_FAST16_MAX
INT_FAST32_MAX INT_FAST32_MIN UINT_FAST32_MAX
INT_FAST64_MAX INT_FAST64_MIN UINT_FAST64_MAX
INTMAX_MAX INTMAX_MIN UINTMAX_MAX
Note the MIN
for all the unsigned types is 0
, so, as such, there’s no macro for it.
In order to print these types, you need to send the right format specifier to printf()
. (And the same issue for getting input with scanf()
.)
But how are you going to know what size the types are under the hood? Luckily, once again, C provides some macros to help with this.
All this can be found in <inttypes.h>
.
Now, we have a bunch of macros. Like a complexity explosion of macros. So I’m going to stop listing out every one and just put the lowercase letter n
in the place where you should put 8
, 16
, 32
, or 64
depending on your needs.
Let’s look at the macros for printing signed integers:
PRIdn PRIdLEASTn PRIdFASTn PRIdMAX PRIin PRIiLEASTn PRIiFASTn PRIiMAX
Look for the patterns there. You can see there are variants for the fixed, least, fast, and max types.
And you also have a lowercase d
and a lowercase i
. Those correspond to the printf()
format specifiers %d
and %i
.
So if I have something of type:
int_least16_t x = 3490;
I can print that with the equivalent format specifier for %d
by using PRIdLEAST16
.
But how? How do we use that macro?
First of all, that macro specifies a string containing the letter or letters printf()
needs to use to print that type. Like, for example, it could be "d"
or "ld"
.
So all we need to do is embed that in our format string to the printf()
call.
To do this, we can take advantage of a fact about C that you might have forgotten: adjacent string literals are automatically concatenated to a single string. E.g.:
("Hello, " "world!\n"); // Prints "Hello, world!" printf
And since these macros are string literals, we can use them like so:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
int_least16_t x = 3490;
printf("The value is %" PRIdLEAST16 "!\n", x);
}
We also have a pile of macros for printing unsigned types:
PRIon PRIoLEASTn PRIoFASTn PRIoMAX
PRIun PRIuLEASTn PRIuFASTn PRIuMAX
PRIxn PRIxLEASTn PRIxFASTn PRIxMAX PRIXn PRIXLEASTn PRIXFASTn PRIXMAX
In this case, o
, u
, x
, and X
correspond to the documented format specifiers in printf()
.
And, as before, the lowercase n
should be substituted with 8
, 16
, 32
, or 64
.
But just when you think you had enough of the macros, it turns out we have a complete complementary set of them for scanf()
!
SCNdn SCNdLEASTn SCNdFASTn SCNdMAX
SCNin SCNiLEASTn SCNiFASTn SCNiMAX
SCNon SCNoLEASTn SCNoFASTn SCNoMAX
SCNun SCNuLEASTn SCNuFASTn SCNuMAX SCNxn SCNxLEASTn SCNxFASTn SCNxMAX
Remember: when you want to print out a fixed size integer type with printf()
or scanf()
, grab the correct corresponding format specifer from <inttypes.h>
.