C Mastery / Types Part 1: Integer Types and Representations
Part 1 — The Core Language

Types Part 1: Integer Types and Representations

This chapter covers every integer type in C, how they are represented in memory, what ranges they guarantee, and how conversions between them behave. It is one of the most portability-sensitive chapters in the language.

Why This Matters

Almost every C bug involving overflow, truncation, sign extension, or a surprising comparison result traces back to a misunderstanding of integer types. Systems and embedded programmers cannot write correct code — much less secure code — without knowing exactly what each integer type guarantees and what it does not.

Prerequisites

Core Concept

The integer types

C has two broad families of integer types: signed and unsigned. The basic integer types are:

TypeSignednessMinimum bits (C99+)Typical bits (LP64)
charimplementation-defined88
signed charsigned88
unsigned charunsigned88
short / short intsigned1616
unsigned shortunsigned1616
intsigned1632
unsigned intunsigned1632
long / long intsigned3264
unsigned longunsigned3264
long long / long long intsigned6464
unsigned long longunsigned6464
_Boolunsigned1 (value range)1
wchar_t, char16_t, char32_timplementation-definedvaries

"Minimum bits" is the minimum *number of value bits plus sign bit* required by the standard; the actual size is implementation-defined and is what sizeof reports in bytes.

A subtlety: char is its own type

char, signed char, and unsigned char are three distinct types. char is the same size as signed char and unsigned char (one byte), but it is a separate type, and whether char is signed or unsigned is implementation-defined.

This matters: on x86 GCC/Clang, char is signed by default; on ARM many toolchains, char is unsigned. Code that depends on char being signed is non-portable.

_Bool

_Bool (available as bool via <stdbool.h> since C99) holds only 0 or 1. Any nonzero value converted to _Bool becomes 1. C23 makes bool, true, and false keywords; in C99–C17 they come from the header.

How It Works

Value ranges

For an integer type with N value bits:

everywhere): range is -(2^(N-1)) to 2^(N-1) - 1.

The standard historically allowed signed integers to be represented in three ways:

1. Two's complement (virtually universal; required by C23). 2. One's complement (obsolete; C17 permitted but rarely used). 3. Sign-and-magnitude (obsolete; C17 permitted but rarely used).

C23 requires two's complement. In C17 and earlier, the representation is implementation-defined but two's complement is the only one you will encounter on real hardware. VERIFIED

Two's complement, precisely

In two's complement with N bits, a bit pattern b represents:

This makes the most negative value -2^(N-1), which has no positive counterpart. Negating it is undefined behavior because the result is not representable.

Padding bits (C17, not C23)

In C17 and earlier, an integer type can have padding bits that do not participate in the value. C23 removed padding bits for standard integer types. In practice, all mainstream compilers have zero padding bits. PARTIALLY VERIFIED (historically real, practically absent today).

Syntax

Declaring integers

int a;
unsigned int b;
long c;
unsigned long long d;
signed char e;
_Bool f;

Suffixes for literals

SuffixType it selects
noneint, else long, else long long (for decimal); also unsigned for octal/hex
u/Uunsigned variant of the candidate
l/Llong variant
ll/LLlong long variant
ul, lu, etc.unsigned long
ull, lluunsigned long long

Example:

42      /* int */
42u     /* unsigned int */
42L     /* long */
42UL    /* unsigned long */
42LL    /* long long */
42ULL   /* unsigned long long */

Examples

Printing integer types portably

Use <inttypes.h> macros for exact-width types:

#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    int32_t  a = -5;
    uint64_t b = 18446744073709551615ULL;

    printf("%" PRId32 "\n", a);
    printf("%" PRIu64 "\n", b);
    return 0;
}

Expected output:

-5
18446744073709551615

The PRId32 and PRIu64 macros expand to the correct format specifier for the platform. This is the only portable way to print exact-width types.

sizeof and limits

#include <stdio.h>
#include <limits.h>

int main(void)
{
    printf("int min: %d\n", INT_MIN);
    printf("int max: %d\n", INT_MAX);
    printf("unsigned int max: %u\n", UINT_MAX);
    printf("sizeof(int): %zu\n", sizeof(int));
    return 0;
}

Expected output is implementation-defined; on a typical LP64 system:

int min: -2147483648
int max: 2147483647
unsigned int max: 4294967295
sizeof(int): 4

<limits.h> provides INT_MIN, INT_MAX, UINT_MAX, CHAR_MIN, CHAR_MAX, SCHAR_MIN, SCHAR_MAX, UCHAR_MAX, SHRT_MIN, SHRT_MAX, USHRT_MAX, LONG_MIN, LONG_MAX, ULONG_MAX, LLONG_MIN, LLONG_MAX, ULLONG_MAX. CHAR_BIT gives the number of bits in a byte (at least 8).

Variations

Fixed-width types (<stdint.h>)

C99 introduced exact-width types. Use these when the width matters (file formats, network protocols, hardware registers):

TypeMeaning
int8_t, uint8_texactly 8 bits (if such a type exists)
int16_t, uint16_texactly 16 bits
int32_t, uint32_texactly 32 bits
int64_t, uint64_texactly 64 bits
intptr_t, uintptr_tlarge enough to hold a pointer
intmax_t, uintmax_tlargest supported integer type
ptrdiff_t (in <stddef.h>)signed result of pointer subtraction
size_t (in <stddef.h>)unsigned result of sizeof

The exact-width types are optional — they exist only if the implementation has a type of exactly that width with no padding. In practice, all modern general-purpose systems provide them, but some DSPs with unusual word sizes do not. VERIFIED

Minimum-width and fastest types

<stdint.h> also provides int_leastN_t (at least N bits) and int_fastN_t (fastest type with at least N bits). Prefer these for general code where an exact width is not required but a minimum is.

Common Mistakes

on 64-bit Linux/macOS, long is 64 bits (LP64).

match; use %zu, %llu, or <inttypes.h> macros.

(c.types.uac).

Undefined Behavior

arithmetic wraps modulo 2^N and is well-defined. VERIFIED

can be UB (c.ops.shift).

Portability

only by the minimum ranges.

ModelintlongpointerCommon on
ILP3232323232-bit Unix, Windows
LP6432646464-bit Linux, macOS
LLP6432326464-bit Windows

Under the Hood

Integer arithmetic maps directly to CPU integer instructions. The compiler chooses the instruction width from the type after the integer promotions and usual arithmetic conversions (c.core.14). A 32-bit add on a 64-bit CPU may be a 32-bit instruction, and the result is truncated to 32 bits.

Two's complement is natural for hardware because addition, subtraction, and multiplication work identically for signed and unsigned at the bit level; only the interpretation of the result differs. This is why unsigned arithmetic is defined to wrap: wrapping is exactly what the hardware does.

Practical Usage

enough for any object).

hardware registers.

Exercises

1. Write a program that prints sizeof and the limits for every basic integer type. Run it on at least two platforms (or cross-compile) and compare. 2. Determine whether char is signed or unsigned on your compiler. Do not rely on the answer in portable code; document how you tested. 3. Write a function that detects whether adding two ints would overflow *without* triggering undefined behavior (check before adding). 4. Demonstrate that INT_MIN negated is UB by writing a program and running it under UBSan (Part 6 covers sanitizers).

Deep Challenge

Implement a small library that safely adds, subtracts, and multiplies signed integers, returning an error on overflow, using only well-defined operations. Explain for each function exactly why your check is correct and does not itself overflow.

References

(limits), §7.20 (stdint.h).

Verification

representation. VERIFIED

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.types.int06