4 ms·
> A serious mistake in C. Well, that's arguable. This "mistake" could be fixed in C tomorrow without breaking the semantics of any existing C code, but notice
by wizeman 4y ago
> A serious mistake in C.
Well, that's arguable. This "mistake" could be fixed in C tomorrow without breaking the semantics of any existing C code, but notice that this hasn't been "fixed" in any of the latest C standards, so perhaps it's still there for a reason.
And the reason it hasn't been "fixed" is that compilers can optimize code better if they can assume that signed addition won't overflow.
So it's more of a trade-off rather than strictly being a disadvantage.
It's also something you can "fix" in your own code if you want to, by passing a compiler flag (-fwrapv in gcc), although arguably, at that point your code wouldn't be strictly C-standard compliant anymore. Or by using some library that handles arithmetic overflow by wrapping around, which could be implemented in standard C.
> Another example is that Go requires explicit integer casts (disallowing implicit integer casts) to avoid what is now understood to be an enormous source of confusion and bugs in C.
I agree with you on this, although forcing explicit casts also makes the code more verbose and can make it harder to understand what's going on.
I think a balanced approach is requiring explicit casts only for the "tricky" cases, i.e. when the values might become truncated, and possibly also when sign-extension might be necessary and therefore might result in something the programmer didn't expect.
But if I were to design a language I'm not sure that I would require explicit casts for e.g. promoting a uint16_t to a uint32_t...
> You can understand Go as an improved C, designed for a world where parallel computing (e.g., dozens of CPU cores) is commonplace.
That's a bit of a hot take :) Let me know when the Linux kernel starts to get rewritten in Go ;)
- Gibbon1 4y ago> And the reason it hasn't been "fixed" is that compilers can optimize code better if they can assume that signed addition won't overflow. Everyone says that and they have no proof.
- wizeman 4y ago> Everyone says that and they have no proof. Here's an entire blog post with proof: https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html https://kristerw.blogspot.com/2016/02/how-undefined-signed-o...
- Gibbon1 4y agoBlog post has theory but no performance measurements.
- wizeman 4y agoWell, then you have moved the goal post, because initially you didn't ask for performance measurements, you asked for proof that compilers can optimize code better if they can assume signed arithmetic won't overflow. And the blog post lists dozens of optimizations which can indeed be performed due to that decision. The benefits of these optimizations will vary depending on the actual code and on the architecture/CPU, of course. It will be greater for hot and tight loops that benefit from these optimizations and less important for cold parts of the code. It will also be greater for more limited CPUs and less important for more sophisticated ones.
- a1369209993 4y agoNone of that requires or even supports undefined overflow. > Signed integer expression simplification [...] This is easily addressed by allowing (but not requiring) implementations to keep wider intermediate results on signed overflow, to a unspecified width at least as wide as the operands. > the index calculations are typically done using 32-bit int No, they're done using size_t; that's what size_t is for. > undefined overflow ensures that a[i] and a[i+1] are adjacent. No, the fact that i (of type size_t) < length of a <= size of a in bytes <= SIZE_MAX ensures that a[i] and a[i+1] are adjacent. (Also, note that this does not mean the optimizer is allowed to access memory at &a[i+1], since that might be across a page boundary (or even the wraparound from address ...FFF to 0), which makes adjacency less helpful than one might hope.) > Value range calculations [...] Loop analysis and optimization [...] Allowed but not required to retain excess bits on signed overflow.
- wizeman 4y ago> > Signed integer expression simplification [...] > This is easily addressed by allowing (but not requiring) implementations to keep wider intermediate results on signed overflow, to a unspecified width at least as wide as the operands. > > Value range calculations [...] Loop analysis and optimization [...] > Allowed but not required to retain excess bits on signed overflow. What does that even mean? That the code would have different behavior with a different compiler, different optimizations or when you slightly change it (depending on whether the compiler chooses to keep wider intermediate results or not)? If understand you correctly, then depending on the values of x and y, `(x+1)<(y+3)` would have a different result than `(x+a)<(y+b)` when a=1 and b=3, because in the first case you would be simplifying the expression but in the second case you couldn't. That would be quite surprising, to say the least. > No, they're done using size_t; that's what size_t is for. for (int i = 0; i < 256; i++) a[i] = b[i] + c[i]; So you've never seen code like this? Not all arrays are huge and for small indices people usually go for `int`. Also, when doing arithmetic with indices, you might need to represent a negative index, so `size_t` wouldn't work. You'd need to use `ssize_t`, which is a signed integer which would benefit from these optimizations. But even then you might use an `int` if you know the arithmetic result will fit. > No, the fact that i (of type size_t) < length of a <= size of a in bytes <= SIZE_MAX ensures that a[i] and a[i+1] are adjacent. Not if `i` is a signed integer, say, `int` or `int8_t`. Which is the point of the optimization.