4 ms·
> But when a.size() == 0, a.size() - 1 is MAXINT. This is exactly the same problem class of bug as mentioned in the original article. But my argument is that
by haberman 12y ago
> But when a.size() == 0, a.size() - 1 is MAXINT.
This is exactly the same problem class of bug as mentioned in the original article.
But my argument is that while signed lengths fix some problems, they cause others.
Maybe I'd put it this way: I dislike using signed integers for lengths, because it creates an entirely new class of undefined (or arbitrarily-defined) behavior.
Imagine that memcpy() took a signed length instead of unsigned. What should it do if it receives a negative length? There is no great answer to that question.
It's true that if you have a bug, you might accidentally pass a very large unsigned integer to memcpy(). But at least you can predict what memcpy() will do now: it will attempt to copy a very large number of bytes and probably crash. Whereas if you pass a negative integer, it might reasonably do any of these things:
- nothing
- crash
- attempt to copy bytes *before* the supplied ptr
- assert()-fail and/or call abort()
What's worse, it creates a sense of anxiety for anyone implementing a function like memcpy(), because there is no right answer for what they should do with a negative length. Callers of memcpy() might reasonably want any of the above. But then you get your callers angry with you because "obviously" a negative length means you should do nothing, or "obviously" a negative length means you should assert()-fail (a programmer's opinion about what is the "obvious" right behavior is often unduly influenced by their specific circumstances).
- __david__ 12y agoIt would be nice to have C/C++ operators/types that failed on overflow/underflow. The sad part is it's available on all processors in the status register. Just no one has brought it up to the higher level language level. If you could declare hard unsigned int x=0; then things like a.size() - 1 could abort() loudly instead of silently doing something that's probably wrong and masking errors later on.
- pcwalton 12y agoRust does have those types, but they aren't free: overflow checking has a significant performance cost even when fully optimized. Because of the performance cost, a lot of code doesn't use the overflow-checking types. I think what we really need is for the hardware manufacturers to help us out here.
- StefanKarpinski 12y agoAgreed – hardware assistance would be a huge boon.
- mkup 12y agoBOUND instruction on x86 architecture exists for time immemorial, but not widely used and may be slow in modern microcode (like AAM/AAD and other stuff of handwritten assembly era, never emitted by compilers).
- __david__ 12y ago> …overflow checking has a significant performance cost even when fully optimized. That surprises me—I would have thought a quick bit-test on the status register and conditional jump wouldn't be all that bad. > I think what we really need is for the hardware manufacturers to help us out here. Are you thinking something like a software interrupt on integer overflow?
- dbaupp 12y agoThere's the secondary effects of missed optimisations too: if every operation is checked for overflow, there's much less scope for reordering/collapsing/vectorising operations, (this is also especially important in tight loops where the core loop can easily be less than 20 instructions, so 2 extra is significant).
- ufo 12y ago> if every operation is checked for overflow, there's much less scope for reordering/collapsing/vectorising operations Lets pretend that instead of "put a conditional jump after every operation", we try to model things as "pretend all integers are infinite precision, blow up if overflow happens at some point". So for example, we would be OK with rewriting int x = MAX_INT int y = (x + 1) - 1 //overflow - BOOM! with int x = MAX_INT int y = x; //no overflow Yes, this means that the program does different things depending on the level of optimization but are there any reordering/vectorizing possibilities that get blocked now?
- StefanKarpinski 12y agoThere's only one reasonable behavior for a negative length to memcpy: raise an error. That would catch exactly this kind of error. I feel like it proves my point rather than contradicting it – if the argument to memcpy is unsigned, you have no way of detecting this programming error; your only option is taking that very large value as literal – which leads to a different and far less helpful error (in the best case).