Step 5 of 8
The signed/unsigned comparison trap
When an expression mixes a signed and an unsigned integer of the same (or larger) size, C converts the signed one to unsigned before operating. For comparisons this produces results that look impossible:
#include <stdio.h>
#include <string.h>
int main(void) {
int n = -1;
size_t len = strlen("abc");
printf("%d\n", len > (size_t)n);
printf("%d\n", n < 0 || len > (size_t)n);
unsigned int u = 1;
printf("%d\n", -1 < (int)u);
return 0;
}
0
1
1
The first line asks "is 3 greater than -1?" and gets false, because -1 converted to size_t is the largest possible value. (The example writes the conversion out as (size_t)n so it compiles without warnings, but plain len > n does exactly the same conversion behind your back.) The second line handles the negative case first, before any conversion happens, and gets the right answer. The third converts the unsigned side to int instead, which is safe here because 1 fits.
Small types are promoted first
Before doing arithmetic, C converts char and short (signed or unsigned) to int. These integer promotions mean small types don't overflow in the middle of an expression:
#include <stdio.h>
int main(void) {
unsigned char a = 200, b = 100;
int sum = a + b; /* both promoted to int: 300 */
unsigned char wrapped = a + b; /* 300 doesn't fit in 1 byte: stored as 300 - 256 = 44 */
printf("%d %d\n", sum, wrapped);
return 0;
}
300 44
The addition happens in int. Only storing the result in the 1-byte wrapped loses information, and since that's a conversion to an unsigned type it wraps around, which is well defined. Promotion is also why printf("%d", c) works for a char: it arrives as an int.
Where this bites
strlen, sizeof and container sizes are all unsigned (size_t). Comparing them with an int that might be negative, or subtracting two sizes (a_len - b_len is huge, not negative, when b is longer), are classic bugs.
Fixes
- Handle the negative case separately before comparing, as in the second line.
- Or convert the unsigned value to a signed type that's wide enough to hold it. Converting a
size_ttointis not safe in general, since a length can exceedINT_MAX. - Compile with
-Wall -Wextra:-Wsign-compareflags these. Treat that warning as a bug report, not noise.
Your turn: longer_than(s, n) should return 1 when s has more than n characters. It's wrong for negative n (every string is longer than -1 characters). Fix it without casting the length to int (strings can be longer than INT_MAX).