Some cases where you should use unsigned integer types are:
- You need to treat a datum as a pure binary representation.
- You need the semantics of modulo arithmetic you get with unsigned numbers.
- You have to interface with code that uses unsigned types (e.g. standard library routines that accept/return
size_t
values.
But for general arithmetic, the thing is, when you say that something "can't be negative," that does not necessarily mean you should use an unsigned type. Because you can put a negative value in an unsigned, it's just that it will become a really large value when you go to get it out. So, if you mean that negative values are forbidden, such as for a basic square root function, then you are stating a precondition of the function, and you should assert. And you can't assert that what cannot be, is; you need a way to hold out-of-band values so you can test for them (this is the same sort of logic behind getchar()
returning an int
and not char
.)
Additionally, the choice of signed-vs.-unsigned can have practical repercussions on performance, as well. Take a look at the (contrived) code below:
#include <stdbool.h>
bool foo_i(int a) {
return (a + 69) > a;
}
bool foo_u(unsigned int a)
{
return (a + 69u) > a;
}
Both foo
's are the same except for the type of their parameter. But, when compiled with c99 -fomit-frame-pointer -O2 -S
, you get:
.file "try.c"
.text
.p2align 4,,15
.globl foo_i
.type foo_i, @function
foo_i:
movl $1, %eax
ret
.size foo_i, .-foo_i
.p2align 4,,15
.globl foo_u
.type foo_u, @function
foo_u:
movl 4(%esp), %eax
leal 69(%eax), %edx
cmpl %eax, %edx
seta %al
ret
.size foo_u, .-foo_u
.ident "GCC: (Debian 4.4.4-7) 4.4.4"
.section .note.GNU-stack,"",@progbits
You can see that foo_i()
is more efficient than foo_u()
. This is because unsigned arithmetic overflow is defined by the standard to "wrap around," so (a + 69u)
may very well be smaller than a
if a
is very large, and thus there must be code for this case. On the other hand, signed arithmetic overflow is undefined, so GCC will go ahead and assume signed arithmetic doesn't overflow, and so (a + 69)
can't ever be less than a
. Choosing unsigned types indiscriminately can therefore unnecessarily impact performance.