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 |
|---|---|---|
| -128 to 127 | 256 |
| 0 to 255 | 256 |
#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.
#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
#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.
#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.
#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)
signedtypes split their range between negative and positive;unsignedtypes 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.