4 ms·
I am generally in the camp of maximally explicit and consistent semantics for integer types. The old C-style types (short, int, etc) and related promotion rules
by jandrewrogers 5y ago
I am generally in the camp of maximally explicit and consistent semantics for integer types. The old C-style types (short, int, etc) and related promotion rules was the source of numerous bugs in practice, particularly when porting code across architectures with different type models. While not convenient in C, in C++ there is almost unlimited ability to define new families of integer types that follow their own rules.
I don't follow the argument for using signed integers outside of contexts where a negative value is valid. In code bases that always use unsigned integers for values that cannot be negative (common in C++), the MAX value is treated as a sentinel. This extends the range of valid values versus signed types, which is sometimes useful, and there will never be undefined behavior (decrementing zero generates the sentinel, for example).
The infinite loop example often given for why to use signed integers doesn't happen in practice for "default unsigned" code bases because that is not idiomatic expression of that case -- and this case will come up even in code bases that are default signed. Writing loops like this is often a code smell.
- andrepd 5y agoHow will there never be undefined behaviour?
- jandrewrogers 5y agoI meant in the sense that unsigned integers have C-specified defined behaviors for conditions like overflow that are undefined for signed integers. That said, if you bothering to create these integers types (e.g. the common MAX sentinel idiom), their behavior is often fully defined. If you go crazy with metaprogramming, type inference, etc in recent C++, you can often verify at compile-time that these conditions will not occur across arithmetic operations, though this is a lot less trivial.
- Jensson 5y ago> I am generally in the camp of maximally explicit and consistent semantics for integer types Size 3 minus size 4 being 4 billion is not correct type semantics though. If unsigned integers crashed the program for those operations or returned something akin to NaN it would be correct. Which means that signed integers are more correct for sizes, since when signed integers overflow you try to allocate a negative length array, and that doesn't work so it crashes, that is what you want. When you do the same thing with unsigned integers you now get a very small array, and when you try to write values to this array you now write them to arbitrary places in memory, causing memory corruption, that is much much worse than any issues you get with signed integers. So the reason to use signed everywhere is that it reduces the mental overhead, and it is safer, you now only needs to learn a single integer math system and don't have to worry about all the issues that comes with casting unsigned and signed values to each other. Unsigned only makes sense if you explicitly want the overflow mathematics of unsigned, you don't want overflow math for array sizes though so they should be signed in any sane type system.
- jandrewrogers 5y agoA negative size is generally not a valid type instance regardless. In well-behaved code I would expect this to be an unreachable condition because you are explicitly checking for sensible inputs before doing this type of arithmetic. It makes no sense to check for sensible inputs after you do the arithmetic, that is the same amount of code done in the wrong order. It is not safer to use signed in this case nor does it decrease mental overhead. Assuming an integer is signed can be much less safe in practice because there is often no guarantee that an integer type is always signed or unsigned in a given context. Typedefs are a ubiquitous thing, and you don't always control those definitions. In code designed to be robust and maintainable, it is usually idiomatic to always implement these things in a way that is correct regardless of signed-ness unless that signed-ness is an immutable contract (e.g. some syscall stuff), because whether or not a type is signed can sometimes change unexpectedly.
- MauranKilom 5y ago> In well-behaved code I would expect this to be an unreachable condition because you are explicitly checking for sensible inputs before doing this type of arithmetic You previously claimed "there will never be undefined behavior [by using unsigned types]", but now you also require that the UB scenario is checked beforehand? I'm confused how you conclude unsigned types to be safer than signed ones ("no UB") when you require UB be ruled out in the first place. Unless you explicitly intend for wraparound to happen, your program has a bug on overflow regardless of signedness. You can (and possibly should) guard against the situation, but that favors neither side. > Assuming an integer is signed can be much less safe in practice because there is often no guarantee that an integer type is always signed or unsigned in a given context. [...] That is not an argument for or against using signed types. If types change from under you, you have a host of other issues anyway (as you say). Mixed sign comparisons, unexpected promotions, overflows... You get the exact same issues whether you yourself favor signed or unsigned types when mixing them with (what are, in your description) effectively unknown types. So again, your argument against signed types appears to be "you have to be careful either way", which gives zero reasons to favor unsigned over signed types.
- xscott 5y ago> [unsigned] extends the range of valid values versus signed types, which is sometimes useful When is it useful? On a 32 bit platform, for file offsets, maybe that last bit was useful for a few years. But as soon as disks got bigger than 4GB, you needed to switch to a 64 bit `off_t` and `lseek` anyways. For array indexes, on a 32 bit platform, there is really only one VERY CONTRIVED case where that last bit for `unsigned` matters: your program creates a single array of bytes greater than 2GB. As soon as it's an array of 2-byte shorts or anything larger, you never need that last bit. And the configurations where a 32 bit kernel lets a program use more than 2GB of address space were not super common - it was better to switch to a 64 bit platform at that point. On 64 bit platforms, you never need that last bit for array indexes or file offsets because no system has 10,000 petabytes of memory or 10,000 petabyte file sizes. And they won't for a while. Unless clock speeds get back on Moore's law, that for loop is going to take a long time to run anyways... > Writing loops like this is often a code smell. One person's code smell is another person's daily idiom. I think signed array indexes are like complex numbers - people who don't need them can't imagine why anyone else does. (https://github.com/golang/go/issues/19921 https://github.com/golang/go/issues/19921) For me, I frequently do math on the array index. Reverse loops are necessary sometimes (I can give examples), but let's ignore that for now. How would you write this simple loop with unsigned loop indices? double scale = <non-integer value> for (ssize_t ii = 0; ii<len; ii++) { data[ii] = sinc(scale*(ii - len/2)); } Note that `len/2` using integer division (truncating) is doing the right thing for both even and odd lengths. I really want that `sinc` function centered on a specific sample. If either `ii` or `len` is unsigned, C/C++ quietly does something awful here. Stuff like this makes me resent the STL for choosing size_t over ssize_t. I think it's ugly in Rust too: let len = data.len(); // usize because that's the way for ii in 0..len { data[ii] = sinc(scale*(ii as isize - len as isize / 2) as f64); } Thankfully the `as` operator binds pretty tightly. I don't think there are any good reasons to use unsigned integers for array indexes or sizes. It doesn't help with bound checking (it's the same assembly to check for unsigned out of bounds as it is to check for signed out of bounds or less than zero). It doesn't help with "large" arrays (except in one very contrived case). And it gets in the way when you need to do math on the indexes.
- tialaramex 5y ago