char can be either signed or unsigned, it’s up to the implementation
In practice, this means “depends on CPU architecture”. Unixes on x86/amd64 keep it signed in order to not break stuff. But signed chars don’t make sense, so on newer architectures like aarch64 and riscv it’s unsigned. As a result… char is one giant portability footgun. I’ve had to send a patch to WebKit (of all things) to fix a signed char issue that broke the build on FreeBSD/aarch64.
so, don’t use char as anything other than an opaque type;
if you need to do actual computations on chars, use unsigned char or signed char;
if the program has already been written with signed chars in mind, put -fsigned-char into CFLAGS!
Although in that case, since int has to be at least 16-bits per the standard, uint8_t + uint8_t must always call the int overload (because it always fits), never the unsigned int overload. On the other hand, while char can do weird things like be as large as int (as described in the post), uint8_t can simply not exist if the implementation doesn’t support it.
In practice, this means “depends on CPU architecture”. Unixes on x86/amd64 keep it signed in order to not break stuff. But signed chars don’t make sense, so on newer architectures like aarch64 and riscv it’s unsigned. As a result…
char
is one giant portability footgun. I’ve had to send a patch to WebKit (of all things) to fix a signed char issue that broke the build on FreeBSD/aarch64.char
as anything other than an opaque type;unsigned char
orsigned char
;-fsigned-char
intoCFLAGS
!This post was also featured in the 200th episode of CppCast starring Herb Sutter.
that’s okay because
char + char
is a nonsense operation, it makes no more sense thandate + date
Why would everyone ever want to know? Isnt this why inttypes.h exists? Is int8_t and uint8_t not more clear?
The problem is that all arithmetic gets promoted to int, using explicitly sized types doesn’t make any difference.
Although in that case, since
int
has to be at least 16-bits per the standard,uint8_t + uint8_t
must always call theint
overload (because it always fits), never theunsigned int
overload. On the other hand, whilechar
can do weird things like be as large asint
(as described in the post),uint8_t
can simply not exist if the implementation doesn’t support it.Because you have two overloaded functions, one that takes a char as an input and one that takes an integer as input.
They have different behaviour, then you need to know which one will resolve :-)