CSigned & Unsigned

Signed & Unsigned

Every integer type in C can be qualified as signed (the default) or unsigned. This choice does not change how many bits are used — it changes how those bits are interpreted, and therefore what range of values the type can represent.

Same bits, different range

For a given bit width, a signed type splits its range between negative and positive numbers, while an unsigned type dedicates the entire range to non-negative numbers. The table below shows this for an 8-bit type.

Type (8-bit example)

Range

Total distinct values

signed

-128 to 127

256

unsigned

0 to 255

256

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

int main(void) {
    printf("signed char range:   %d to %d\n", SCHAR_MIN, SCHAR_MAX);
    printf("unsigned char range: 0 to %d\n", UCHAR_MAX);
    return 0;
}
signed char range:   -128 to 127
unsigned char range: 0 to 255
Unsigned overflow wraps around (well-defined)

Unsigned integer arithmetic in C is defined to wrap around using modular arithmetic: if you go below zero, it wraps to the top of the range, and if you go above the maximum, it wraps back to zero. This behavior is guaranteed by the standard, not a compiler quirk.

C
#include <stdio.h>

int main(void) {
    unsigned char u = 255;
    u = u + 1;                 /* wraps around: well-defined */
    printf("%d\n", u);        /* prints 0 */

    unsigned char u2 = 0;
    u2 = u2 - 1;                /* wraps the other way: well-defined */
    printf("%d\n", u2);       /* prints 255 */
    return 0;
}
0
255
Signed overflow is undefined behavior
Signed integer overflow is UB — not a wraparound
Unlike unsigned overflow, signed integer overflow is explicitly undefined behavior in C. In practice this often produces wraparound on common hardware (which is why many beginners assume it behaves like the unsigned case), but the standard makes no such guarantee. A compiler is legally permitted to assume signed overflow never happens, and aggressive optimizers really do exploit this — code that relies on signed overflow wrapping can be silently miscompiled, produce inconsistent results between debug and release builds, or even eliminate checks you wrote specifically to catch the overflow. If you need wraparound arithmetic, use an unsigned type on purpose.

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

int main(void) {
    int max = INT_MAX;
    int result = max + 1;   /* undefined behavior, NOT guaranteed to wrap */
    printf("%d\n", result); /* unreliable: don't rely on this "working" */
    return 0;
}
The classic signed/unsigned comparison bug

When a signed and an unsigned value of the same rank are compared with <, >, ==, and similar operators, C's usual arithmetic conversions silently convert the signed value to unsigned before comparing. If the signed value was negative, this conversion turns it into a very large unsigned number, producing a comparison result that looks completely wrong at first glance.

C
#include <stdio.h>

int main(void) {
    int signedVal = -1;
    unsigned int unsignedVal = 1;

    /* BROKEN: -1 is converted to a huge unsigned number before comparing */
    if (signedVal < unsignedVal) {
        printf("-1 is less than 1 (expected)\n");
    } else {
        printf("-1 is NOT less than 1 (surprising!)\n");
    }
    return 0;
}
-1 is NOT less than 1 (surprising!)

The fix is to avoid mixing signed and unsigned types in the same comparison — either keep both operands signed, or explicitly cast with intent, checking for negativity first.

C
#include <stdio.h>

int main(void) {
    int signedVal = -1;
    unsigned int unsignedVal = 1;

    /* FIXED: guard against negative values before comparing as unsigned */
    if (signedVal < 0 || (unsigned int)signedVal < unsignedVal) {
        printf("-1 is less than 1 (correct)\n");
    }
    return 0;
}
-1 is less than 1 (correct)
  • signed types split their range between negative and positive; unsigned types use the full range for non-negative values only.

  • Unsigned overflow wraps around and is well-defined by the standard.

  • Signed overflow is undefined behavior — never rely on it wrapping.

  • Comparing a signed and unsigned value silently converts the signed value to unsigned, which can produce very surprising results for negative numbers.