Others have gone into the historical reasons for it to have been this way when C was first devised and (later) standardised, but there's another reason why this seeming anomaly persists to this day.
It's simply that when you're using char
for characters, you don't need to know whether it's signed or unsigned. The standard library provides portable functions for operating on characters regardless of their representation. If you ignore those functions and insist on doing comparisons and arithmetic on characters, you deserve every bug you get.
To take a simple example, it's quite commonplace to check whether a character is printable using the expression c >= ' '
or equivalently c >= 0x20
, but you should just use isprint(c)
instead. That way, you're not exposing yourself to signed/unsigned confusion and potentially introducing platform-dependent errors into your program.
Once you get into the habit of using signed char
and unsigned char
only as small (usually 8-bit) integers for arithmetic, and you use only char
when you're operating on character data, it'll seem completely natural that char
is a separate type with implementation-defined signedness, and even more natural that string processing functions always use char
and char *
rather than the signed or unsigned variants. The signedness of char
seems about as relevant as the signedness of bool
.