Using signed or unsigned int by default in C (2015)
blog.robertelder.org
blog.robertelder.org
- 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.
https://trust-in-soft.com/blog/2016/05/20/objects-larger-tha...
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.
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.)
And part 2: https://news.ycombinator.com/item?id=10156265
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".
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:
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
When using unsigned ints for backward iteration, I'm partial to looping as:
for (i = size - 1; i < size; i--) {
...
}
since it has so much symmetry with forward iteration. for(uint32_t i = 0; i < length; ++i) {
i = (length - 1) - i;
// ...
}
Much easier for me to understand, making it less likely to mess anything up. foreach (i; 0 .. length)
D takes care of selecting the correct type for `i`.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.
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...
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.
Even more so, I find myself using particular sizes of unsigned ints (uint8_t, uint16_t, etc) rather than the default "unsigned int"
_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".
It’s not as if a program with a variable called interest_rate or ford_model_t would be nonconforming according to the standard.
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.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.
It's 64 on Tru64 UNIX systems, for instance.