signed and unsigned types
Think of a car's odometer versus a thermometer. An odometer only counts up from 0 and never shows a negative number — that is like an unsigned type. A thermometer can read below zero — that is like a signed type. In C every integer type comes in these two flavours, and the flavour decides whether negative values are even possible and how the same bits are read.
An unsigned type uses all of its bits to represent magnitude, so an 8-bit unsigned char ranges from 0 to 255. A signed type reserves the top bit (the sign bit) to mark negativity, so an 8-bit signed char ranges from -128 to 127 using two's complement. The bit pattern 0xFF is 255 read as unsigned but -1 read as signed — same bits, different interpretation. You request signedness with the keywords unsigned and signed (for example, unsigned int, signed char). Plain int is signed; plain char is its own case whose signedness is implementation-defined, which is exactly why people write signed char or unsigned char when they care.
Why this matters and a sharp caveat: unsigned arithmetic WRAPS by definition — it is well-defined modular arithmetic, so 0u - 1u gives the maximum value, on purpose. But SIGNED integer overflow is undefined behavior in C; the hardware may wrap, yet the standard imposes no requirement, so the optimizer may assume it never happens. Never conflate the two. A classic bug: comparing a signed value against an unsigned one makes the signed value convert to unsigned first, so a negative number becomes huge and the comparison surprises you.
unsigned char u = 0; u = u - 1; /* well-defined: wraps to 255 */ signed char s = 127; s = s + 1; /* UNDEFINED BEHAVIOR: signed overflow */
Unsigned underflow wraps predictably; signed overflow is undefined behavior even though the CPU would likely wrap too.
Mixing signed and unsigned in a comparison silently converts the signed operand to unsigned, so a negative number becomes a large positive one. Enable -Wsign-compare and be deliberate about which types you compare.