As @SomeProgrammerDude explained and you already knew, you specify signed or unsigned explicitly when you wish to use small integral values. On an AMD-64 architecture (which is the most widely-used architecture for 64-bit general-purpose CPUs, and it probably the one you have on your laptop), a signed char takes up 1 byte and ranges from -128 to 127, while an unsigned char also takes up 1 byte but ranges from 0 to 255.
I would like to push this a little further by showing how using signed or unsigned integral types (such as char, short, int, ...) impact the final program and are actually implemented. I'll use the example of char, but the principle is identical with other integral types.
Assume we have this small program:
// main.cpp
#include <iostream>
int main() {
signed char sc = (signed char)255; // equivalent to sc = -1
unsigned char uc = 255;
bool signedComp = (sc <= 5);
bool unsignedComp = (uc <= 5);
return 0;
}
If we have a look at the assembler (the code that is very close to what your CPU actually does), we can observe the difference. Here is the most relevant part of the assembler code:
movb $-1, -4(%rbp) # sc = -1
movb $-1, -3(%rbp) # uc = -1 (equivalent to uc = 255)
cmpb $5, -4(%rbp) # compare sc and 5, ...
setle %al # ... see whether sc was lower or equal (signed comparison), ...
movb %al, -2(%rbp) # ... and set the boolean result into signedComp.
cmpb $5, -3(%rbp) # compare uc and 5, ...
setbe %al # ... see whether uc was below or equal (unsigned comparison), ...
movb %al, -1(%rbp) # ... and set the boolean result into unsignedComp.
(If you are curious and want to generate the assembler yourself, run g++ -S main.cpp -o main.s -O0 and have a look at part of the main.s file where you see the main: tag.)
On your memory (specifically, on the stack), both sc and uc will take up 1 byte. In fact, sc and uc actually contain the same value of 255. However, it's the way the comparison is done that makes sc and uc different.
Thus, this :
there isn't much of a difference between an unsigned char and a signed char
... is ironically 100% true.
The lesson to learn from this is that the numbers programmers work with are just conceptual. In the end, it's all about how you work with the 1s and 0s.
charis signed or not. So you would usesigned char(orunsigned char) if you want a small integer in the range of it. But then I would rather recommendint8_tanduint8_tinstead (if available) for generic small integers.