The terms signed and unsigned refer to how the CPU treats sequences of bits.
There are 2 important things to understand here:
- How the CPU adds finite sequences of bits to a single finite result
- How the CPU differentiates between signed and unsigned operands
Let's start with (1).
Let's take 4-bit nibbles for example.
If we ask the CPU to add 0001
and 0001
, the result should be 2, or 0010
.
But if we ask it to add 1111
and 0001
, the result should be 16, or 10000
. But it only has 4 bits to contain the result. The convention is to wrap-around, or circle, back to 0, effectively ignoring the Most Significant Bit. See also integer overflow..
Why is this relevant? Because it produces an interesting result. That is, according to the definition above, if we let x = 1111
, then we get x + 1 = 0
. Well, x
, or 1111
, now looks and behaves awfully like -1
. This is the birth of signed numbers and operations. And if 1111
can be deemed as -1
, then 1111 - 1 = 1110
should be -2
, and so on.
Now let's look at (2).
When the C compiler sees you defining an unsigned int
, it will use special CPU instructions for dealing with unsigned numbers, where it deems relevant. For example, this is relevant in jump
instructions, where the CPU needs to know if you mean it to jump way forward, or slightly backward. For this it needs to know if you mean your operand to be interpreted in a signed, or unsigned way.
The operation of adding two numbers, on the other hand, is fundamentally oblivious to the consequent interpretation. The only thing is that the CPU will turn on a special flag after an addition operation, to tell you whether a wrap-around has occurred, for your own auditing.
But the important thing to understand is that the sequence of bits doesn't change; only its interpretation.
To tie all of this to your example, subtracting 1
from an unsigned 0
will simply wrap-around back to 1111
, or 2^32 in your case.
Finally, there are other uses for signed/unsigned. For example, by the very fact it is defined as a different type, this allows functions to be written that define a contract where only unsigned integers, let's say, can be passed to it. Also, it's relevant when you want to display or print the number.