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:
#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; }
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>:
| Type | Exactly |
|---|---|
int8_t / uint8_t | 8 bits, signed / unsigned |
int16_t / uint16_t | 16 bits |
int32_t / uint32_t | 32 bits |
int64_t / uint64_t | 64 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:
#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; }
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.
#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; }
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:
#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; }
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:
#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; }
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
| Literal | Meaning |
|---|---|
42 | Decimal int |
0x2A | Hexadecimal (42) |
052 | Octal (42) — a leading zero means octal! |
42u, 42L, 42LL, 42ULL | unsigned, long, long long, unsigned long long |
0b101010 | Binary (C23; a common extension before that) |
#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; }
42 42 42 hex: ff, HEX: FF, octal: 10
Choosing a type
intfor everyday counting and arithmetic with modest values.size_tfor sizes, lengths and array indexes.int64_t/long longwhen values may exceed about two billion (money in cents, timestamps, file sizes).- Exact-width types for binary formats and hardware.
unsignedtypes 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.
3500000000 bytes 3500 MB