Loading learning content...
On June 4, 1996, the Ariane 5 rocket—a flagship project of the European Space Agency—exploded 37 seconds after launch. The 10-year, $7 billion development program was destroyed in an instant. The cause? A 64-bit floating-point number was converted to a 16-bit signed integer. The velocity value exceeded 32,767, the conversion overflowed, and the guidance system received garbage data. The rocket swerved violently and self-destructed.
This wasn't a hardware failure. It wasn't bad luck. It was integer overflow—a predictable, preventable bug that occurs when a calculation exceeds the maximum (or goes below the minimum) value representable in a given integer type.
Understanding integer ranges and overflow behavior is not optional knowledge for serious engineers. It's foundational to writing correct, secure software.
By the end of this page, you will understand: (1) The exact ranges of integers at different bit widths, (2) Why overflow happens and how to predict it, (3) The difference between signed overflow (undefined behavior) and unsigned wraparound, (4) How different languages handle overflow, and (5) Patterns for detecting and preventing overflow in practice.
Before we can understand overflow, we must have absolute clarity on the ranges that different integer types can represent. Every integer type has a finite range determined by its bit width.
| Bits | Unsigned Min | Unsigned Max | Signed Min | Signed Max |
|---|---|---|---|---|
| 8 | 0 | 255 | -128 | 127 |
| 16 | 0 | 65,535 | -32,768 | 32,767 |
| 32 | 0 | 4,294,967,295 | -2,147,483,648 | 2,147,483,647 |
| 64 | 0 | 18,446,744,073,709,551,615 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
The mathematical formulas:
For an n-bit integer:
| Type | Minimum | Maximum | Total Values |
|---|---|---|---|
| Unsigned | $0$ | $2^n - 1$ | $2^n$ |
| Signed | $-2^{n-1}$ | $2^{n-1} - 1$ | $2^n$ |
Important named constants:
In C/C++, you'll frequently encounter these named constants (defined in <limits.h> or <climits>):
CHAR_MAX, CHAR_MIN — 8-bit: 127 and -128 (or 255 and 0 for unsigned char)SHRT_MAX, SHRT_MIN — 16-bit: 32,767 and -32,768INT_MAX, INT_MIN — 32-bit: 2,147,483,647 and -2,147,483,648LLONG_MAX, LLONG_MIN — 64-bit: 9,223,372,036,854,775,807 and -9,223,372,036,854,775,808Memorization tips:
Knowing these ranges isn't academic trivia. When someone says their user count is "about a billion," you should immediately think: "That's half of INT_MAX. We need to consider whether 32-bit signed integers are sufficient." This mental reflex prevents catastrophic bugs.
Integer overflow occurs when the result of an arithmetic operation exceeds the range of the integer type being used. There are two directions:
The fundamental problem:
When you add 1 to the maximum value of an integer type, something must happen—but there's no bit pattern available to represent the correct result. The outcome depends on:
123456789101112131415161718192021222324
#include <stdio.h>#include <limits.h>#include <stdint.h> int main() { // Signed overflow (undefined behavior in C/C++) int32_t signed_max = INT32_MAX; // 2,147,483,647 printf("INT32_MAX = %d\n", signed_max); printf("INT32_MAX + 1 = %d\n", signed_max + 1); // Undefined! Often wraps to -2,147,483,648 // Unsigned wraparound (well-defined behavior) uint32_t unsigned_max = UINT32_MAX; // 4,294,967,295 printf("UINT32_MAX = %u\n", unsigned_max); printf("UINT32_MAX + 1 = %u\n", unsigned_max + 1); // Always 0 (wraps around) // Underflow examples int32_t signed_min = INT32_MIN; // -2,147,483,648 printf("INT32_MIN - 1 = %d\n", signed_min - 1); // Undefined! Often wraps to +2,147,483,647 uint32_t unsigned_zero = 0; printf("0u - 1 = %u\n", unsigned_zero - 1); // Always 4,294,967,295 (wraps around) return 0;}In C and C++, signed integer overflow is explicitly undefined behavior. The compiler is allowed to assume it never happens. This enables optimizations, but means overflow can cause unpredictable results—including seemingly impossible behavior. The program might crash, produce wrong results, or appear to work until a different compiler version is used.
The behavior of overflow differs fundamentally between signed and unsigned integers. This distinction is critical for writing correct code.
-fwrapv for gcc)Why is signed overflow undefined?
This might seem strange—why would the C standard leave behavior undefined rather than specifying wraparound (which is what the hardware does)?
Optimization opportunities: If the compiler can assume overflow never happens, it can perform powerful optimizations. For example:
x + 1 > x can be optimized to true (if we assume no overflow)Historical diversity: Early machines used different signed representations (sign-magnitude, one's complement). Undefined behavior allowed portability across these systems.
Detection opportunities: By not defining the behavior, implementations can choose to trap (crash) on overflow, enabling debugging.
The practical reality:
Most hardware performs two's complement wraparound for signed overflow, just like unsigned. But relying on this is dangerous because:
A famous example: if you write if (x + 1 < x) to detect overflow in signed integers, the compiler may optimize this check away entirely—deciding that since signed overflow is undefined, the condition can never be true. Your overflow check simply disappears from the compiled code.
A powerful mental model for understanding overflow is the number circle (or number wheel). Instead of imagining integers on a line extending to infinity, visualize them arranged in a circle.
8-bit unsigned number circle:
0
255 1
254 2
253 3
... ...
129 127
128
Incrementing moves clockwise. Decrementing moves counter-clockwise. When you increment 255, you wrap around to 0.
8-bit signed number circle (two's complement):
0
-1 1
-2 2
-3 3
... ...
-127 127
-128
Notice that -128 and 127 are adjacent. Incrementing 127 crosses into -128—the wraparound point.
The key insight:
Both signed and unsigned share the same circle (the same 256 bit patterns for 8-bit). The difference is only in where we cut the circle open to assign names:
Unsigned arithmetic is exactly modular arithmetic modulo 2^n. Every operation wraps around the circle. This is why unsigned overflow is well-defined—it's not really "overflow" at all; it's just the natural behavior of modular arithmetic. The term "overflow" implies going beyond boundaries, but in modular arithmetic, there are no boundaries.
When does subtraction overflow?
For unsigned: a - b overflows (underflows) when b > a. The result wraps around from 0 to the maximum.
For signed: a - b overflows when:
a > 0, b < 0, and a - b exceeds max positive (positive overflow)a < 0, b > 0, and a - b goes below min negative (negative overflow)When does multiplication overflow?
Multiplication can overflow even with relatively small operands:
50000 × 50000 = 2,500,000,000 — exceeds 32-bit signed max (~2.1 billion)70000 × 70000 = 4,900,000,000 — also exceeds 32-bit unsigned max (~4.3 billion)When does addition overflow?
For unsigned: a + b overflows when a + b ≥ 2^n
For signed: a + b overflows when both have the same sign and the result has the opposite sign
Different programming languages handle integer overflow in fundamentally different ways. Understanding your language's behavior is essential.
| Language | Signed Overflow | Unsigned Overflow | Detection Options |
|---|---|---|---|
| C/C++ | Undefined behavior | Wrap around (defined) | Compiler sanitizers, manual checks |
| Java | Wrap around (defined) | N/A (no unsigned types) | Math.addExact() throws exception |
| Python | Auto-promotes to arbitrary precision | Same as signed | Never overflows—unlimited precision |
| JavaScript | N/A (uses 64-bit float) | N/A (uses 64-bit float) | Loses precision beyond 2^53 |
| Rust | Panic in debug, wrap in release | Panic in debug, wrap in release | checked_, saturating_, wrapping_* |
| Go | Wrap around (defined) | Wrap around (defined) | Manual detection required |
| Swift | Trap (crash) by default | Trap (crash) by default | &+ operator for wraparound |
| C# | Wrap by default | Wrap by default | checked { } block to trap |
Language deep-dive: Rust's approach
Rust exemplifies a thoughtful approach to overflow:
// Standard operators panic in debug mode, wrap in release mode
let x: u8 = 255;
let y = x + 1; // Panic in debug! Wraps to 0 in release.
// Explicit overflow handling methods
let wrapped = x.wrapping_add(1); // Always wraps: 0
let saturated = x.saturating_add(1); // Clamps: 255
let checked = x.checked_add(1); // Returns Option: None
let (result, overflow) = x.overflowing_add(1); // Returns (value, bool)
This forces developers to be explicit about their intent, catching bugs during development while still allowing performance-critical release builds.
Python integers have arbitrary precision—they grow to accommodate any value. While this prevents overflow bugs, it can cause unexpected memory usage and performance issues. Multiplying two large numbers might suddenly allocate megabytes of memory for the result.
1234567891011121314151617181920212223
// Java overflow behavior demonstrationpublic class OverflowDemo { public static void main(String[] args) { // Java signed integers wrap around silently int maxInt = Integer.MAX_VALUE; // 2,147,483,647 System.out.println("MAX_VALUE + 1 = " + (maxInt + 1)); // -2,147,483,648 // Java 8+ provides checked arithmetic methods try { int result = Math.addExact(maxInt, 1); // Throws ArithmeticException } catch (ArithmeticException e) { System.out.println("Overflow detected: " + e.getMessage()); } // Multi-word multiplication overflow (easy to miss) int bigA = 100000; int bigB = 100000; int wrongProduct = bigA * bigB; // Overflow! 1410065408 (not 10,000,000,000) long correctProduct = (long)bigA * bigB; // Correct: 10,000,000,000 System.out.println("Wrong: " + wrongProduct); System.out.println("Correct: " + correctProduct); }}Preventing overflow requires proactive detection. Here are robust techniques for different operations.
Technique 1: Pre-operation checks
Check before the operation whether it would overflow:
// Safe signed addition: check before adding
bool safe_add_int(int a, int b, int *result) {
if (b > 0 && a > INT_MAX - b) return false; // Would overflow positive
if (b < 0 && a < INT_MIN - b) return false; // Would overflow negative
*result = a + b;
return true;
}
// Safe signed multiplication: check before multiplying
bool safe_mult_int(int a, int b, int *result) {
if (a > 0 && b > 0 && a > INT_MAX / b) return false;
if (a > 0 && b < 0 && b < INT_MIN / a) return false;
if (a < 0 && b > 0 && a < INT_MIN / b) return false;
if (a < 0 && b < 0 && a < INT_MAX / b) return false;
*result = a * b;
return true;
}
Technique 2: Use wider types for intermediate calculations
Temporarily promote to a larger type, perform the calculation, then check if the result fits:
bool safe_add_int32(int32_t a, int32_t b, int32_t *result) {
int64_t wide_result = (int64_t)a + (int64_t)b;
if (wide_result > INT32_MAX || wide_result < INT32_MIN) {
return false; // Would overflow
}
*result = (int32_t)wide_result;
return true;
}
This approach is cleaner and often faster than pre-checks, but doesn't work when you're already using the widest available type.
Technique 3: Compiler built-ins
Modern compilers provide overflow-checking intrinsics:
// GCC/Clang built-ins
int a = 1000000000, b = 2000000000, result;
if (__builtin_add_overflow(a, b, &result)) {
printf("Overflow detected!\n");
} else {
printf("Sum: %d\n", result);
}
// Similar for multiplication
if (__builtin_mul_overflow(a, b, &result)) {
printf("Multiplication overflow!\n");
}
These compile to efficient code that uses hardware overflow detection flags.
GCC and Clang provide -fsanitize=undefined which instruments your code to detect undefined behavior at runtime, including signed overflow. This is invaluable during development and testing. Always run your test suite with sanitizers enabled.
Let's examine real-world scenarios where overflow commonly occurs, even in experienced developers' code.
malloc(count * sizeof(element)) — if count is large, multiplication overflows and a tiny buffer is allocated, leading to buffer overflow vulnerabilitiesseconds = hours * 3600 * 24 * days — time durations easily overflow 32-bit integerscents = dollars * 100 — currency amounts can exceed integer rangemid = (low + high) / 2 — the classic binary search bug where low + high overflowsx * width + y for large images1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
#include <stdio.h>#include <stdlib.h>#include <stdint.h> // BUG: Classic binary search midpoint overflowint binary_search_buggy(int arr[], int n, int target) { int low = 0, high = n - 1; while (low <= high) { int mid = (low + high) / 2; // BUG: overflows if low + high > INT_MAX if (arr[mid] == target) return mid; if (arr[mid] < target) low = mid + 1; else high = mid - 1; } return -1;} // FIX: Overflow-safe midpoint calculationint binary_search_safe(int arr[], int n, int target) { int low = 0, high = n - 1; while (low <= high) { int mid = low + (high - low) / 2; // SAFE: no overflow possible // Alternative: mid = (low & high) + ((low ^ high) >> 1); // Bit trick if (arr[mid] == target) return mid; if (arr[mid] < target) low = mid + 1; else high = mid - 1; } return -1;} // BUG: malloc size calculation overflowvoid* allocate_array_buggy(size_t count) { // If count = 2^30 and sizeof(int) = 4, this overflows to 0 return malloc(count * sizeof(int)); // Allocates tiny or zero-size buffer!} // FIX: Check for overflow before allocationvoid* allocate_array_safe(size_t count) { if (count > SIZE_MAX / sizeof(int)) { return NULL; // Would overflow } return malloc(count * sizeof(int));} int main() { printf("Binary search demos with large arrays would show overflow...\n"); return 0;}The binary search midpoint overflow bug existed in the Java standard library for nearly a decade before being discovered in 2006. Billions of binary searches using (low + high) / 2 had executed without issue because most arrays were small enough. This demonstrates how overflow bugs can lurk undetected until edge cases are triggered.
Integer overflow isn't just a correctness issue—it's a major source of security vulnerabilities. Attackers specifically look for overflow bugs because they can often be exploited for code execution.
12345678910111213141516171819202122232425262728293031323334353637
#include <stdlib.h>#include <string.h>#include <stdint.h> // VULNERABLE: Classic heap overflow via integer overflowvoid process_network_data_vulnerable(uint32_t element_count, const void* data) { // Attacker sends element_count = 0x40000001 (about 1 billion) // element_count * 4 = 0x100000004, which truncates to 4 in 32-bit size_t buffer_size = element_count * sizeof(uint32_t); // Overflows! uint32_t* buffer = malloc(buffer_size); // Allocates only 4 bytes! if (buffer == NULL) return; // Now copies 4 billion bytes into a 4-byte buffer -> heap overflow memcpy(buffer, data, element_count * sizeof(uint32_t)); // CRASH or exploit // ... process buffer ... free(buffer);} // SECURE: Overflow check before allocationint process_network_data_safe(uint32_t element_count, const void* data) { // Check for multiplication overflow if (element_count > SIZE_MAX / sizeof(uint32_t)) { return -1; // Reject: would overflow } size_t buffer_size = element_count * sizeof(uint32_t); uint32_t* buffer = malloc(buffer_size); if (buffer == NULL) return -1; memcpy(buffer, data, buffer_size); // ... process buffer ... free(buffer); return 0;}The CVE (Common Vulnerabilities and Exposures) database contains thousands of integer overflow vulnerabilities. Major ones include: OpenSSH's challenge-response overflow (CVE-2002-0639), Windows GDI+ JPEG overflow (CVE-2004-0200), and countless image parsing vulnerabilities in browsers and media players. These aren't obscure bugs—they've been exploited in the wild.
Integer overflow is a fundamental issue that every programmer must understand. Let's consolidate the key points:
What's next:
Having understood the range limitations and overflow behavior, we'll explore fixed-size storage—why computers use specific bit widths (8, 16, 32, 64) and how this affects both memory layout and performance. This knowledge connects integer representation to practical system design.
You now understand integer overflow at a deep level—not just that it happens, but why, how to detect it, and its real-world consequences. This knowledge is essential for writing robust, secure software.