Integer overflow is the kind of bug that works perfectly during development and silently corrupts data in production. The difference between signed and unsigned overflow is a language-level distinction with serious security implications — buffer overflows, incorrect loop conditions, and exploitable vulnerabilities all stem from misunderstanding this.

Unsigned Overflow — Defined and Wraps

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

int main() {
    uint8_t x = 255;
    x++;
    printf("%un", x);   /* 0 — wraps to zero */

    uint32_t y = 0;
    y--;
    printf("%un", y);   /* 4294967295 (UINT32_MAX) */

    return 0;
}

Unsigned integer overflow is explicitly defined in the C standard. It wraps modulo 2^N where N is the bit width. UINT8_MAX + 1 == 0 is guaranteed. You can rely on this for bitmasks, hash functions, and circular buffers.

Signed Overflow — Undefined Behavior

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

int main() {
    int x = INT_MAX;   /* 2,147,483,647 on 32-bit int */
    x++;
    printf("%dn", x);   /* undefined behavior */
    return 0;
}

Signed integer overflow is undefined behavior. On most platforms it wraps to INT_MIN at runtime, but the compiler is allowed to assume it never happens and optimize away any code that would only execute on overflow. Never write code that depends on signed wrapping.

The practical consequence: a loop like for (int i = 0; i <= INT_MAX; i++) may become an infinite loop because the compiler proves i <= INT_MAX is always true for a valid int and removes the termination check.

The Classic Buffer-Overflow Setup

#include <stdlib.h>
#include <string.h>

void process(size_t a, size_t b) {
    /* size_t is unsigned — if a + b wraps, allocation is too small */
    char *buf = malloc(a + b);
    if (buf == NULL) return;

    /* if a + b wrapped to a small number, memset writes past buf */
    memset(buf, 0, a + b);

    free(buf);
}

int main() {
    /* a + b = 0x80000001 + 0x80000001 = 0x100000002, wraps to 2 on 32-bit */
    process(0x80000001UL, 0x80000001UL);
    return 0;
}

This is a real vulnerability class. The allocation is too small because the size wrapped. The subsequent write uses the original (large) sum and writes past the end of the buffer. Attackers can craft inputs to trigger exactly this pattern.

Safe Overflow Detection

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

/* Check before adding — works for signed integers */
int safe_add(int a, int b, int *result) {
    if (b > 0 && a > INT_MAX - b) return -1;  /* overflow */
    if (b < 0 && a < INT_MIN - b) return -1;  /* underflow */
    *result = a + b;
    return 0;
}

int main() {
    int result;
    if (safe_add(INT_MAX, 1, &result) == -1) {
        printf("overflow detectedn");
    } else {
        printf("result: %dn", result);
    }
    return 0;
}
/* Check before adding — simpler for unsigned */
int safe_add_unsigned(size_t a, size_t b, size_t *result) {
    if (a > SIZE_MAX - b) return -1;   /* would wrap */
    *result = a + b;
    return 0;
}

GCC Built-in Overflow Checks (GCC 5+)

#include <stdio.h>

int main() {
    int a = 2000000000, b = 2000000000;
    int result;

    if (__builtin_add_overflow(a, b, &result)) {
        printf("overflow!n");
    } else {
        printf("result: %dn", result);
    }
    return 0;
}

GCC provides __builtin_add_overflow, __builtin_sub_overflow, and __builtin_mul_overflow for all integer types. These generate a single instruction on x86 (using the carry/overflow flag) and are faster than manual checks. They also work on unsigned types, making them the best option when you need portable, fast overflow detection. These work in our c online compiler which uses GCC 4.8.

Promotion Rules — The Unexpected Overflow

#include <stdio.h>

int main() {
    unsigned short a = 60000;
    unsigned short b = 60000;

    /* Both promoted to int (signed) before multiplication */
    /* 60000 * 60000 = 3,600,000,000 — overflows signed int */
    unsigned int result = a * b;   /* UB even though result is unsigned */

    printf("%un", result);   /* implementation-dependent */
    return 0;
}

Integer promotion rules bite you here. Both unsigned short values are promoted to int (signed) before the multiplication. The product overflows int — undefined behavior — before the assignment to unsigned int even happens. The fix: cast before multiplying.

unsigned int result = (unsigned int)a * (unsigned int)b;   /* defined */

TL;DR

  • Unsigned overflow is defined — it wraps modulo 2^N
  • Signed overflow is undefined behavior — never rely on wrapping semantics for signed types
  • Check before adding, not after — test a > INT_MAX - b rather than a + b > INT_MAX
  • Use __builtin_add_overflow on GCC for fast, correct overflow detection
  • Watch out for integer promotion — short * short overflows as signed int before the result is stored
  • Use bitwise operators and unsigned types for bitmask/flag operations to avoid the signed overflow trap
  • In security-critical code, validate all external integer inputs against both lower and upper bounds before arithmetic