This is an historical design bug of C that was also repeated in C++.
It dates back to 16-bit computers and the error was deciding to use all 16 bits to represent sizes up to 65536 giving up the possibility to represent negative sizes.
This in se wouldn't have been an error if unsigned
meaning was "non-negative integer" (a size cannot logically be negative) but it's a problem with the conversion rules of the language.
Given the conversion rules of the language the unsigned
type in C doesn't represent a non-negative number, but it's instead more like a bitmask (the mathematical term is actually "a member of the ℤ/n ring"). To see why consider that for the C and C++ language
unsigned - unsigned
gives an unsigned
result
signed + unsigned
gives and unsigned
result
both of them clearly make no sense at all if you read unsigned
as "non-negative number".
Of course saying that the size of an object is a member of ℤ/n
ring doesn't make any sense at all and here it's where the error resides.
Practical implications:
Every time you deal with the size of an object be careful because the value is unsigned
and that type in C/C++ has a lot of properties that are illogical for a number. Please always remember that unsigned
doesn't mean "non-negative integer" but "member of ℤ/n
algebraic ring" and that, most dangerous, in case of a mixed operation an int
is converted to unsigned int
and not the opposite.
For example:
void drawPolyline(const std::vector<P2d>& pts) {
for (int i=0; i<pts.size()-1; i++) {
drawLine(pts[i], pts[i+1]);
}
}
is buggy, because if passed an empty vector of points it will do illegal (UB) operations. The reason is that pts.size()
is an unsigned
.
The rules of the language will convert 1
(an integer) to 1{mod n}
, will perform the subtraction in ℤ/n
resulting in (size-1){mod n}
, will convert i
also to a {mod n}
representation and will do the comparison in ℤ/n
.
C/C++ actually defines a <
operator in ℤ/n
(rarely done in math) and you will end up accessing pts[0]
, pts[1]
... and so on until huge numbers even if the input vector was empty.
A correct loop could be
void drawPolyline(const std::vector<P2d>& pts) {
for (int i=1; i<pts.size(); i++) {
drawLine(pts[i-1], pts[i]);
}
}
but I normally prefer
void drawPolyline(const std::vector<P2d>& pts) {
for (int i=0,n=pts.size(); i<n-1; i++) {
drawLine(pts[i], pts[i+1]);
}
}
in other words getting rid of unsigned
as soon as possible, and just working with regular ints.
Never use unsigned
to represent size of containers or counters because unsigned
means "member of ℤ/n
" and the size of a container is not one of those things. Unsigned types are useful, but NOT to represent size of objects.
The standard C/C++ library unfortunately made this wrong choice, and it's too late to fix it. You are not forced to do the same mistake however.
In the words of Bjarne Stroustrup:
Using an unsigned instead of an int to gain one more bit to represent
positive integers is almost never a good idea. Attempts to ensure that
some values are positive by declaring variables unsigned will
typically be defeated by the implicit conversion rules