I'd argue that is a terrible way to write it since it depends on the rollover to terminate. Using i >= 0 is less mental work but also requires that i be signed.
- When iterating an array, you're supposed to use "size_t", because it's the only type that's guaranteed to contain the maximum size of an array.
- When storing the difference between two pointers, you're supposed to use "ptrdiff_t" because it's the only type that's guaranteed to contain the signed difference between two related pointers.
- When storing numbers, prefer unsigned to signed because signed overflow is UB.
> - When storing numbers, prefer unsigned to signed because signed overflow is UB.
There's an interesting caveat to this one - if you rely on sanitizers like ubsan to catch overflow errors, using unsigned won't work. It will happily and silently overflow.
For what it's worth (though you likely know it), you can add "-fsanitize=unsigned-integer-overflow" to trigger a diagnostic even if unsigned integer overflow is not UB.
Of course, this only works if your code does not rely on unsigned integer overflow or if you suppress each case one by one.
I would tend to say this is a good practice to enable it, because the number of times where you rely on the overflow should be very low and well documented.
I had forgotten about that one - that's a great solution, and I agree that in most programs unsigned overflow is rarely desirable, so explicit annotation makes sense for those cases.
This all makes sense at first sight, but remember that we can always take the difference between the address of an array's first element and one past the end, so you run into a lot of issues when an array (or any object) is larger than PTRDIFF_MAX bytes.
IMO the third point is backwards. Because unsigned overflow is defined you cannot detect it with tools nearly as well but it still almost certainly represents a bug in your program. Better to find the bug with ubsan or some static analyzer than silently produce broken behavior.
The mathematical definition of "integer" includes the set of negative integers. That's the whole point, otherwise we could just use natural numbers. So making ints signed makes the most sense otherwise there's an impedance mismatch that you need to explain away.
The numbers used in computing systems are normal mathematical numbers in the Z_2^32 field. The signed ones are shifted by half the range in the negative direction. There is nothing un-mathematical about them, people who have studied any math in university, i.e. all CS graduates, should have gone through an introductory algebra course that covers fields, rings, etc. So I'm not sure why people keep saying ints are not mathematical.
That depends on the computing system. You're right if we're talking about the processor level (on any modern processor), but the "signed ints" in C or C++ are not a field at all, since INT_MAX + 1 is undefined, as are -INT_MIN and INT_MIN - 1 (and many many many multiplications). Also, as someone else was pointing out, unsigned short arithmetic is not well defined either.
To be honest, I adapted a phrase I usually use for floating point numbers.
But anyway, "So making ints signed makes the most sense otherwise there's an impedance mismatch that you need to explain away" is a pretty poor argument when you're dealing with Z_2^32 (or 2^64) minus 2^31, with some operations becoming partial. (And that's assuming you're on a twos-complement machine; those goofy ones-complement boxes had positive and negative zero.)
I know what you mean and I'm not saying this with any animus, but this is pretty funny:
> people who have studied any math in university, i.e. all CS graduates, should have gone through an introductory algebra course that covers fields, rings, etc.
> The mathematical definition of "integer" is unbounded above and below.
which is exactly why signed overflow is UB: if you use int, you say "I want something that behaves exactly like a mathematical integer in exchange of the promise of not working with quantities too large"
I agree with the content of this article, but all the same my advice is usually the opposite - for reasons that its author sets out rather well in the table in his follow-up article (https://blog.robertelder.org/signed-or-unsigned-part-2/)
- if your profession is "C programmer" and you really know what you're doing, you probably want to use unsigneds most of the time
- if your profession is something else, e.g. signal processing researcher who has to translate an algorithm to C or C++, you are probably better off not using unsigneds at all.
I think I agree with you, as much as I don't want to.
I am a C programmer, and I think I do know what I am doing. (I implemented safe, UB-free two's-complement arithmetic in C using only unsigned integers.) I prefer unsigned.
But for someone who doesn't know, as long as they're not working on anything critical, signed might be "good enough".
The _t suffix is reserved for types defined by the standard. Although this is an excess of pedantry, strictly speaking you should not be defining names ending in _t.
For those wondering: that phrase has to be read as “C99 and later reserve identifiers ((starting with "int" or "uint") and ending in "_t")”, not as “C99 and later reserve identifiers starting with "int" or "uint" and identifiers ending in "_t"”
It’s not as if a program with a variable called interest_rate or ford_model_t would be nonconforming according to the standard.
While this is true, I don't see either a reason to use _t in your code. Since it's your code, you don't have the problem for which the _t suffix was introduced, that is to avoid conflicts that can break other people code. You don't either get any benefit of using the _t suffix, since it's obvious if something is a type or not, especially nowadays (just look at the syntax coloring). You don't even need the _t to distinguish for example between a struct, enum or union definition and it's relative typedef:
typedef struct MyStruct { ... } MyStruct;
is perfectly valid, and in my opinion better than:
typedef struct MyStruct_s { ... } MyStruct_t;
Where the suffix _s and _t doesn't add anything useful but only complicate the code for nothing.
Calling it just "uint8" could conflict with software which already used it as an identifier, or worse, a macro; on the other hand, names ending with _t are reserved (see for instance https://www.gnu.org/software/libc/manual/html_node/Reserved-...), so they could be used by the standard for these new types.
For me, the main reason to prefer unsigned integers whenever possible is: less special cases.
When a signed integer is used as an array index, the value can be in one of three ranges: the <0 range, the >=0 && <size range, and the >=size range. To validate the index, you need two comparisons. When an unsigned integer is used as an array index, there are only two ranges: the <size range, and the >=size range, and you need only a single comparison.
And that's not the worst situation with signed integers. From a more general point of view, there are four "classes" of signed integer: positive (>0), zero, negative (<0), and INT_MIN. Everybody tends to forget about that last one, but it's "special" in that it can break things in unexpected ways. Negating it doesn't work (you'd expect negating any number less than zero to result in a number greater than zero, but for INT_MIN that doesn't happen). Dividing it can trap (see for instance https://kqueue.org/blog/2012/12/31/idiv-dos/ which has a couple of examples) or worse.
With unsigned integers, there are only two "classes", zero and non-zero, and it's common to not even need special treatment for zero, reducing the whole thing to a single "class" of values.
> When a signed integer is used as an array index, the value can be in one of three ranges: the <0 range, the >=0 && <size range, and the >=size range. To validate the index, you need two comparisons. When an unsigned integer is used as an array index, there are only two ranges: the <size range, and the >=size range, and you need only a single comparison.
but the need for the <= 0 case does not disappear - it's a property of integers (Z) whether you want it or not, and the very second you do an arithmetic operation such as a - b, you expect this to behave like numbers:
void compute(int* array, size_t len, /* any integral type */ x) {
size_t res = len - x;
if(res < len)
printf("%d", array[res])
}
this code is wrong an awful lot of time because there isn't actually "a single range" just because the integer is unsigned - you have to make sure that `len >= x` if you want a correct result so there will be comparisons there instead, but those are much trickier to get right from my experience finding bugs in such code.
and if you are going to say that "this rarely happens" - here is an excerpt of a set of simple cases I could find just by grepping on the projects I have cloned where using unsigned will cause crashes: https://pastebin.com/sTgGPV9k
I don't know if I am too stupid or too smart, but I use signed when some number can be negative and unsigned when it cannot. Of course I am aware of their differences other e.g. when they wrap, and act accordingly.
Personally, I choose to just write non-standard C and turn on my compiler's option to define signed overflow as wrapping.
Note that without that option, multiplication of unsigned shorts is undefined on platforms where sizeof(unsigned short) = sizeof(int)/2. That is due to the integer promotion rules. Let's say shorts are 16-bit and ints are 32-bit, which is the case on all consumer devices. Integer promotion rules lead to both unsigned short operands being first converted to ints before multiplication. If they are both equal to 0xffff, then the result is bigger than INT_MAX, and thus undefined. Which means that just multiplying two unsigned shorts is dangerous unless you cast them to unsigned ints first. Here is a godbolt link demonstrating the problem: https://gcc.godbolt.org/z/3445PEGqY
I have never seen the benefit of leaving signed overflow undefined, so I just make it defined to avoid weird problems like that. Most people think that all unsigned arithmetic is well defined, but unfortunately that's false.
That's fine. Define it as saturating on those architectures. Just being defined as "this never happens" is hugely detrimental to predicting how you're program behaves.
> I have never seen the benefit of leaving signed overflow undefined,
IIRC it affords huge optimization opportunities, esp. around loops. (Now, whether you like that or not is a value judgment, but a lot of people do want those optimizations.)
The argument against unsigned is that people abuses unsigned as a way of false safety, as a form of assert. They think the number cannot go below zero or above maxvalue. But in practice it can under- and overflow, so this is a bogus assert. I think there is a valid point in this.
So, if someone does x=(0-1), signed will contain (-1) and unsigned will contain (MAXVALUE) (255 if x is char, 8 bits). The point being that (-1) is clearly wrong and invalid, and 255 is valid but wrong. The price to pay for this is one bit of reduced range (meaning, reducing the range by half).
Now, all modern processors have under- and overflow checks. It is also true that nobody uses them regularly, and that they are not easily accessible in C.
Therefore: use unsigned only for modulo arithmetic. For the rest, used signed numbers, as they behave like you would expect.
64 comments
[ 5.1 ms ] story [ 120 ms ] threadWhen using unsigned ints for backward iteration, I'm partial to looping as:
since it has so much symmetry with forward iteration.When most people see 0 - 1 they don't immediately think 11111111111111111111111111111111
Probably formatted i-- > 0. Concise and reads (once used to it anyway) something like i decrementing to zero.
It's 64 on Tru64 UNIX systems, for instance.
It’s even operating system dependent:
It’s 32-bits on 32-bit x86 unix-likes and windows, as well as 32-bit on 64-bit windows.
On 64-bit unix-likes it’s 64-bit.
Embedded is so varied (and generally 16 or 32 bit), that I’m not even go to hazard a guess on the distribution there.
- When iterating an array, you're supposed to use "size_t", because it's the only type that's guaranteed to contain the maximum size of an array.
- When storing the difference between two pointers, you're supposed to use "ptrdiff_t" because it's the only type that's guaranteed to contain the signed difference between two related pointers.
- When storing numbers, prefer unsigned to signed because signed overflow is UB.
There's an interesting caveat to this one - if you rely on sanitizers like ubsan to catch overflow errors, using unsigned won't work. It will happily and silently overflow.
I would tend to say this is a good practice to enable it, because the number of times where you rely on the overflow should be very low and well documented.
https://trust-in-soft.com/blog/2016/05/20/objects-larger-tha...
And part 2: https://news.ycombinator.com/item?id=10156265
The "numbers" used in computing systems are not mathematical numbers in any way.
But anyway, "So making ints signed makes the most sense otherwise there's an impedance mismatch that you need to explain away" is a pretty poor argument when you're dealing with Z_2^32 (or 2^64) minus 2^31, with some operations becoming partial. (And that's assuming you're on a twos-complement machine; those goofy ones-complement boxes had positive and negative zero.)
> people who have studied any math in university, i.e. all CS graduates, should have gone through an introductory algebra course that covers fields, rings, etc.
> Z_2^32 field
which is exactly why signed overflow is UB: if you use int, you say "I want something that behaves exactly like a mathematical integer in exchange of the promise of not working with quantities too large"
- function
- set
- map
- monad
- vector
- field
- class
Some of them are obviously referring to something else entirely. Some of them are kind of like the math thing but subtly different.
The term is used to mean “multi-dimensional array” in the machine learning community.
That is however not a tensor.
I wrote a little piece about this myself once (http://soundsoftware.ac.uk/c-pitfall-unsigned.html)
I suppose I would loosely characterise my view as
- if your profession is "C programmer" and you really know what you're doing, you probably want to use unsigneds most of the time
- if your profession is something else, e.g. signal processing researcher who has to translate an algorithm to C or C++, you are probably better off not using unsigneds at all.
I am a C programmer, and I think I do know what I am doing. (I implemented safe, UB-free two's-complement arithmetic in C using only unsigned integers.) I prefer unsigned.
But for someone who doesn't know, as long as they're not working on anything critical, signed might be "good enough".
_t acts as a namespace, and that's all namespacing you can get in C.
Some people also use '_e' for enums, etc.
Either way, in general I'd agree this is an excess of pedantry and I don't really see an issue with using _t for types if that works for you.
POSIX reserves everything ending in _t. C99 and later reserve identifiers starting with "int" or "uint" and ending in "_t".
Source: https://en.cppreference.com/w/c/language/identifier
It’s not as if a program with a variable called interest_rate or ford_model_t would be nonconforming according to the standard.
For me, the main reason to prefer unsigned integers whenever possible is: less special cases.
When a signed integer is used as an array index, the value can be in one of three ranges: the <0 range, the >=0 && <size range, and the >=size range. To validate the index, you need two comparisons. When an unsigned integer is used as an array index, there are only two ranges: the <size range, and the >=size range, and you need only a single comparison.
And that's not the worst situation with signed integers. From a more general point of view, there are four "classes" of signed integer: positive (>0), zero, negative (<0), and INT_MIN. Everybody tends to forget about that last one, but it's "special" in that it can break things in unexpected ways. Negating it doesn't work (you'd expect negating any number less than zero to result in a number greater than zero, but for INT_MIN that doesn't happen). Dividing it can trap (see for instance https://kqueue.org/blog/2012/12/31/idiv-dos/ which has a couple of examples) or worse.
With unsigned integers, there are only two "classes", zero and non-zero, and it's common to not even need special treatment for zero, reducing the whole thing to a single "class" of values.
but the need for the <= 0 case does not disappear - it's a property of integers (Z) whether you want it or not, and the very second you do an arithmetic operation such as a - b, you expect this to behave like numbers:
this code is wrong an awful lot of time because there isn't actually "a single range" just because the integer is unsigned - you have to make sure that `len >= x` if you want a correct result so there will be comparisons there instead, but those are much trickier to get right from my experience finding bugs in such code.and if you are going to say that "this rarely happens" - here is an excerpt of a set of simple cases I could find just by grepping on the projects I have cloned where using unsigned will cause crashes: https://pastebin.com/sTgGPV9k
Note that without that option, multiplication of unsigned shorts is undefined on platforms where sizeof(unsigned short) = sizeof(int)/2. That is due to the integer promotion rules. Let's say shorts are 16-bit and ints are 32-bit, which is the case on all consumer devices. Integer promotion rules lead to both unsigned short operands being first converted to ints before multiplication. If they are both equal to 0xffff, then the result is bigger than INT_MAX, and thus undefined. Which means that just multiplying two unsigned shorts is dangerous unless you cast them to unsigned ints first. Here is a godbolt link demonstrating the problem: https://gcc.godbolt.org/z/3445PEGqY
I have never seen the benefit of leaving signed overflow undefined, so I just make it defined to avoid weird problems like that. Most people think that all unsigned arithmetic is well defined, but unfortunately that's false.
The benefit is that architectures are free to implement it with saturating arithmetic.
IIRC it affords huge optimization opportunities, esp. around loops. (Now, whether you like that or not is a value judgment, but a lot of people do want those optimizations.)
Even more so, I find myself using particular sizes of unsigned ints (uint8_t, uint16_t, etc) rather than the default "unsigned int"
So, if someone does x=(0-1), signed will contain (-1) and unsigned will contain (MAXVALUE) (255 if x is char, 8 bits). The point being that (-1) is clearly wrong and invalid, and 255 is valid but wrong. The price to pay for this is one bit of reduced range (meaning, reducing the range by half).
Now, all modern processors have under- and overflow checks. It is also true that nobody uses them regularly, and that they are not easily accessible in C.
Therefore: use unsigned only for modulo arithmetic. For the rest, used signed numbers, as they behave like you would expect.
Also, the C++ Core Guidelines: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC... https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...