Integer Types in Depth

Integers come in signed and unsigned versions and several widths. Choosing the right one — and knowing what happens when a value no longer fits — prevents some of C's most common and subtle bugs.

Signed and unsigned

A signed integer can be negative; an unsigned one can't, but can count twice as high. Plain int, short, long and long long are signed; add unsigned for the other version:

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

int main(void) {
    printf("int:          %d to %d\n", INT_MIN, INT_MAX);
    printf("unsigned int: 0 to %u\n", UINT_MAX);
    printf("long long:    %lld to %lld\n", LLONG_MIN, LLONG_MAX);
    printf("char:         %d to %d\n", CHAR_MIN, CHAR_MAX);
    return 0;
}
Output
int:          -2147483648 to 2147483647
unsigned int: 0 to 4294967295
long long:    -9223372036854775808 to 9223372036854775807
char:         -128 to 127

<limits.h> defines the limits for your platform. Note the different printf specifiers: %u for unsigned, %lld for long long, %ld for long.

Fixed-width types

When the exact size matters — file formats, network protocols, hardware registers — use <stdint.h>:

TypeExactly
int8_t / uint8_t8 bits, signed / unsigned
int16_t / uint16_t16 bits
int32_t / uint32_t32 bits
int64_t / uint64_t64 bits
size_t (from <stddef.h>)Unsigned, big enough for any object size — used for sizes and indexes

To print them portably, <inttypes.h> provides format macros that are pasted into the format string:

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

int main(void) {
    uint8_t brightness = 200;
    int64_t world_population = 8200000000;
    printf("Brightness: %" PRIu8 "\n", brightness);
    printf("Population: %" PRId64 "\n", world_population);
    return 0;
}
Output
Brightness: 200
Population: 8200000000

Unsigned wrap-around

Unsigned arithmetic is defined to wrap around: going past the maximum starts again at 0, and going below 0 jumps to the maximum.

C
#include <stdio.h>
#include <stdint.h>

int main(void) {
    uint8_t counter = 254;
    counter++;
    printf("%d\n", counter);
    counter++;
    printf("%d\n", counter);

    unsigned int items = 0;
    items--;
    printf("%u\n", items);
    return 0;
}
Output
255
0
4294967295

This is well-defined, but often a bug — think of "0 items minus 1" becoming four billion. Be especially careful with loops that count down using unsigned variables.

Signed overflow is undefined

Going past INT_MAX with a signed integer is undefined behaviour (lesson 54): the C standard says nothing about the result, and optimising compilers assume it never happens. Check before the operation instead of after:

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

bool can_add(int a, int b) {
    if (b > 0 && a > INT_MAX - b) return false;
    if (b < 0 && a < INT_MIN - b) return false;
    return true;
}

int main(void) {
    printf("%d\n", can_add(2000000000, 100000000));
    printf("%d\n", can_add(2000000000, -100000000));
    return 0;
}
Output
1
1

When values may be large, simply choose a wider type (int64_t or long long) from the start.

Mixing signed and unsigned

When a signed and an unsigned value of the same size meet in an expression, the signed one is converted to unsigned — so -1 becomes a huge number. Compilers warn about comparisons like this with -Wextra:

C
#include <stdio.h>

int main(void) {
    int balance = -1;
    unsigned int limit = 10;
    if (balance < limit) {
        printf("under the limit\n");
    } else {
        printf("over the limit?!\n");
    }
    return 0;
}
Compiler warnings
main.c:6:17: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]

Run anyway, it would print "over the limit?!". Keep signed and unsigned values apart, or convert explicitly.

Integer literals

LiteralMeaning
42Decimal int
0x2AHexadecimal (42)
052Octal (42) — a leading zero means octal!
42u, 42L, 42LL, 42ULLunsigned, long, long long, unsigned long long
0b101010Binary (C23; a common extension before that)
C
#include <stdio.h>

int main(void) {
    printf("%d %d %d\n", 42, 0x2A, 052);
    printf("hex: %x, HEX: %X, octal: %o\n", 255, 255, 8);
    return 0;
}
Output
42 42 42
hex: ff, HEX: FF, octal: 10

Choosing a type

  • int for everyday counting and arithmetic with modest values.
  • size_t for sizes, lengths and array indexes.
  • int64_t / long long when values may exceed about two billion (money in cents, timestamps, file sizes).
  • Exact-width types for binary formats and hardware.
  • unsigned types for bit manipulation (lesson 14), not just "because it can't be negative".

Try it yourself

A file is 3,500,000,000 bytes. Store its size in a type that can hold it and print it in bytes and in whole megabytes.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
3500000000 bytes
3500 MB