<cstdint>
Fixed-width integer type aliases and limit macros for portable, size-explicit integer programming in C++.
<cstdint>since C++11A standard library header providing platform-independent type aliases for integers with guaranteed widths, along with macros for their minimum, maximum, and literal values.
Overview
When int is 16 bits on one platform and 32 bits on another, code that assumes a particular width silently breaks. <cstdint> solves this by providing type aliases with exact widths baked into their names. The header was formally incorporated into C++ with C++11, sourced from C99's <stdint.h>.
Four families of integer aliases are provided, each covering widths 8, 16, 32, and 64 bits:
| Family | Purpose | Example |
|---|---|---|
intN_t / uintN_t | Exact width; present if the platform supports it | uint32_t |
int_leastN_t / uint_leastN_t | Smallest type with at least N bits; always present | uint_least16_t |
int_fastN_t / uint_fastN_t | Fastest type with at least N bits; always present | int_fast32_t |
intmax_t / uintmax_t | Widest integer type the implementation supports | uintmax_t |
Two optional aliases, intptr_t and uintptr_t, hold a pointer value without loss of information. All major hosted implementations provide them, but the standard does not require it.
All aliases live in namespace std. The header also exposes the C limit and literal macros (INT32_MAX, UINT64_C(), etc.) in the global namespace β these are preprocessor macros and are not in std::.
Syntax
#include <cstdint>
// Exact-width signed
std::int8_t std::int16_t std::int32_t std::int64_t
// Exact-width unsigned
std::uint8_t std::uint16_t std::uint32_t std::uint64_t
// Minimum-width β always available
std::int_least8_t std::int_least16_t std::int_least32_t std::int_least64_t
std::uint_least8_t std::uint_least16_t std::uint_least32_t std::uint_least64_t
// Fastest minimum-width β always available
std::int_fast8_t std::int_fast16_t std::int_fast32_t std::int_fast64_t
std::uint_fast8_t std::uint_fast16_t std::uint_fast32_t std::uint_fast64_t
// Widest integer
std::intmax_t std::uintmax_t
// Pointer-sized β optional, present on all major platforms
std::intptr_t std::uintptr_t
// Limit macros (global namespace)
INT8_MIN INT8_MAX UINT8_MAX
INT16_MIN INT16_MAX UINT16_MAX
INT32_MIN INT32_MAX UINT32_MAX
INT64_MIN INT64_MAX UINT64_MAX
INTMAX_MIN INTMAX_MAX UINTMAX_MAX
// Integer constant macros β produce a literal of exactly the right type
INT8_C(v) INT16_C(v) INT32_C(v) INT64_C(v)
UINT8_C(v) UINT16_C(v) UINT32_C(v) UINT64_C(v)
INTMAX_C(v) UINTMAX_C(v)Examples
Binary protocol and network packet parsing
When you lay out a struct to match an on-wire format, exact widths are non-negotiable:
#include <cstdint>
#include <bit> // C++20
#include <cstring>
struct UDPHeader {
std::uint16_t src_port;
std::uint16_t dst_port;
std::uint16_t length;
std::uint16_t checksum;
};
static_assert(sizeof(UDPHeader) == 8);
UDPHeader parse_udp(const std::byte* buf) {
UDPHeader h;
std::memcpy(&h, buf, sizeof(h));
// Convert big-endian network byte order to host order
h.src_port = std::byteswap(h.src_port); // C++23
h.dst_port = std::byteswap(h.dst_port); // C++23
h.length = std::byteswap(h.length); // C++23
h.checksum = std::byteswap(h.checksum); // C++23
return h;
}The static_assert makes the size assumption explicit and catches unexpected padding at compile time rather than at runtime in the field.
Hardware register access and IP address overlays
Union overlays between raw integers and structured fields are a common pattern in systems programming:
#include <cstdint>
#include <format> // C++20
union IPv4Address {
struct { std::uint8_t a, b, c, d; } octets;
std::uint32_t raw;
};
void print_ip(std::uint32_t addr_le) {
IPv4Address ip;
ip.raw = addr_le;
// std::format β C++20
std::println("{}.{}.{}.{}", ip.octets.a, ip.octets.b, ip.octets.c, ip.octets.d);
}Enum underlying types
Since C++11, scoped enums can specify an underlying type. Fixed-width types make the ABI contract explicit:
#include <cstdint>
enum class Status : std::uint32_t { // C++11 β size is exactly 4 bytes everywhere
OK = 0,
Error = 1,
Timeout = 2,
};
enum class Flags : std::uint8_t { // C++11 β fits in one byte, no padding waste
None = 0x00,
Read = 0x01,
Write = 0x02,
Execute = 0x04,
};Forward-declaring a scoped enum with an underlying type lets the compiler know its size without seeing the enumerator list, enabling use across translation unit boundaries without dragging in the full definition.
Integer constant macros in constexpr and templates
INT32_C and friends produce a literal of exactly the specified type. Without them, an unadorned numeric literal is int, which can silently truncate or trigger UB in constant expressions:
#include <cstdint>
// Without UINT64_C, 0x1FFFF is int β value is fine here, but the intent is lost
constexpr std::uint64_t MAX_PRIME = UINT64_C(0x1FFFF);
template <std::uint32_t N>
struct Factorial {
static constexpr std::uint32_t value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<UINT32_C(0)> {
static constexpr std::uint32_t value = UINT32_C(1);
};
static_assert(Factorial<10>::value == 3628800);Lock-free atomics over fixed-width types
std::atomic specialisations over fixed-width types are lock-free on any platform where the hardware supports the native width natively:
#include <cstdint>
#include <atomic>
// C++11: likely lock-free on x86/ARM for 32- and 64-bit widths
std::atomic<std::uint32_t> counter{0};
// C++17: check lock-freedom at compile time
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}is_always_lock_free (C++17) is true for uint32_t and uint64_t on every mainstream 64-bit architecture.
Best Practices
Use exact types when the width is a contract. Serialisation formats, hardware registers, network protocols, and persistent file layouts all have fixed binary layouts. Using uint32_t makes that contract explicit; using int leaves it to ABI luck and compiler flags.
Prefer the std:: qualified form. std::uint32_t is unambiguous and avoids the edge cases that arise when the C macros and typedefs bleed into the global namespace via transitive includes. The qualification is also a visual cue that the type is a guaranteed-width alias, not a built-in.
For pure performance, consider int_fast32_t. When you need at least 32 bits but do not care about the exact size, int_fast32_t lets the implementation choose the most efficient width. On most 64-bit Linux and macOS systems, int_fast32_t is 64 bits, giving better alignment properties on those targets.
Lock layout assumptions into static_assert. Whenever a struct matches an external binary format, assert sizeof and offsetof at compile time:
#include <cstdint>
#include <cstddef>
struct FileHeader {
std::uint32_t magic;
std::uint16_t version;
std::uint16_t flags;
};
static_assert(sizeof(FileHeader) == 8);
static_assert(offsetof(FileHeader, version) == 4);Common Pitfalls
Exact-width types are technically optional. The standard requires int8_t only if the platform has a two's-complement type of exactly 8 bits with no padding bits. Every hosted implementation on x86, ARM, RISC-V, and other mainstream architectures provides all eight exact-width types. Freestanding or exotic embedded targets may not; int_least8_t is the safe fallback since it is always defined.
int_fast8_t is not necessarily 8 bits. On many 64-bit Linux glibc implementations, int_fast8_t is long (64 bits). Code that assumes sizeof(int_fast8_t) == 1 is non-portable and will misbehave when iterated over arrays or used in packed structures. Use uint8_t when you need exactly one byte.
Format specifiers for printf/scanf live in <cinttypes>, not here. Printing a uint64_t with printf requires PRIu64 from <cinttypes>:
#include <cstdint>
#include <cinttypes>
#include <cstdio>
std::uint64_t n = UINT64_C(12345678901234);
std::printf("%" PRIu64 "\n", n); // correct on all platforms
// std::printf("%lu\n", n); // UB on Windows (LLP64): long is 32 bits thereWith C++20's std::format or C++23's std::println, the type system eliminates this class of error entirely β prefer them in new code.
Do not using-alias short names in headers. Aliases like using i32 = std::int32_t; in a header pollute every translation unit that includes it. Keep such conveniences in .cpp files or within a tightly scoped namespace.
See Also
<cinttypes>β format macros (PRId32,SCNu64) forprintf/scanfwith fixed-width types<limits>βstd::numeric_limits<T>provides the same bounds as the C macros in a type-safe, template-friendly form (C++98)<atomic>β lock-free operations over fixed-width integer specialisations (C++11)<bit>β bit-manipulation utilities includingstd::byteswap(C++23) andstd::popcount(C++20), which pair naturally with fixed-width types