Under what circumstances would one use a signed char in C++?
The reason is that you don't know, at least portably, if plain char
variables are signed or unsigned. Different implementations have different approaches, a plain char
may be signed in one platform and unsigned in another.
If you want to store negative values in a variable of type char
, you absolutely must declare it as signed char
, because only then you can be sure that every platform will be able to store negative values in there. Yes, you can use [u]int8
type, but this was not always the case (it was only introduced in C++11), and in fact, int8
is most likely an alias for signed char
.
Moreover, uint8_t
and int8_t
are defined to be optional types, meaning you can't always rely on its existence (contrary to signed char
). In particular, if a machine has a byte unit with more than 8 bits, it is not very likely that uint8_t
and int8_t
are defined (although they can; a compiler is always free to provide it and do the appropriate calculations). See this related question: What is int8_t if a machine has > 8 bits per byte?
Is char
signed or unsigned?
Actually it is neither, it's implementation defined if a variable of type char
can hold negative values. So if you are looking for a portable way to store negative values in a narrow character type explicitly declare it as signed char
.
§ 3.9.1 - Fundamental Types -
[basic.fundamental]
1 Objects declared as characters (
char
) shall be large enough to store any member of the implementation's basic character set. If a character from this set is stored in a character object, the integral value of that character object is equal to the value of the single character literal form of that character. It is implementation-defined whether achar
object can hold negative values.
I'd like to use the smallest signed integer type available, which one is it?
c++11 introduced several fixed with integer types, but a common misunderstanding is that these types are guaranteed to be available, something which isn't true.
§ 18.4.1 - Header
<cstdint>
synopsis -[cstdint.syn]
typedef
signed integer type
int8_t; // optional
To preserve space in this post most of the section has been left out, but the optional rationale applies to all {,u}int{8,16,32,64}_t
types. An implementation is not required to implement them.
The standard mandates that int_least8_t
is available, but as the name implies this type is only guaranteed to have a width equal or larger than 8 bits.
However, the standard guarantees that even though signed char
, char
, and unsigned char
are three distinct types[1] they must occupy the same amount of storage and have the same alignment requirements.
After inspecting the standard further we will also find that sizeof(char)
is guaranteed to be 1
[2] , which means that this type is guaranteed to occupy the smallest amount of space that a C++ variable can occupy under the given implementation.
Conclusion
Remember that unsigned char
and signed char
must occupy the same amount of storage as a char
?
The smallest signed integer type that is guaranteed to be available is therefore signed char
.
[note 1]
§ 3.9.1 - Fundamental Types -
[basic.fundamental]
1 Plain
char
,signed char
, andunsigned char
are three distinct types, collectively called narrow character types.A
char
, asigned char
, and anunsigned char
occupy the same amount of storage and have the same alignment requirements (3.11
); that is, they have the same object representation. For narrow character types, all bits of the object representation participate in the value representation.
[note 2]
§ 5.3.3 - Sizeof -
[expr.sizeof]
sizeof(char)
,sizeof(signed char)
, andsizeof(unsigned char)
are 1.The result of
sizeof
applied to any other fundamental type (3.9.1) is implementation-defined.