Find me one system in current use (2020) that isn't 2's complement.
> you just made the language more complex for them.
Ah, but I was never talking about implementation complexity. I was talking about specification complexity. Imposing 2's complement makes the specification simpler. No special case, no boundary. Just a binary field. Undefined behaviour draws such a boundary. An extra detail to specify and remember. Some thing that reminds you you're not quite doing modular arithmetic.
> Eh. You did nothing about undefined behavior. It's still there.
I did. There's less of it. I've filled the bug, here's what's this about: https://github.com/jedisct1/libsodium/issues/937
Here's the relevant quote from annex J of the (heavy, I have printed it) C99 specifications:
Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object. (6.5.6)
That's what caused the bug: we were taking a pointer from an array, then used it to overflow past it. It didn't matter that we could access the memory there. What mattered is that we locally overflew that array. Had we computed the pointer by first working with the outer array, then pointed to the right element, we'd have the same pointer, except this time it wouldn't be undefined.
Bonus note: you don't even have to dereference the pointer, computing its value is enough to trigger undefined behaviour.
Here is some more undefined behaviour I have removed (it's subtle, relatively few programmers know this):
Pointers that do not point into, or just beyond, the same array object are subtracted. (6.5.6)
There go flat memory models. Though to be honest, even segmented memory models could define that. Just make the subtraction already, it doesn't have to mean anything. Make it unspecified or implementation defined at least.
Pointers that do not point to the same aggregate or union (nor just beyond the same array object) are compared using relational operators (6.5.8)
That's the reason we can't implement memmove() portably and efficiently. And depending which tool you ask, even converting the pointers to intptr_t first isn't enough. Real world example here: https://github.com/jedisct1/libsodium/commit/e7e378fad116c18...
---
If you haven't already, go read the annex J from the C standard. Ponder the fact that compilers, unless you specifically ask not to (-fwrapv), will exploit every single one, even on platform that could define some reasonable behaviour (2's complement machines). It may hint at how hostile C is to programmers who care about correctness.
Believe me, I know. I wrote a whole Crypto library[1], and that's one of the easiest things to get right, where C is concerned. I mean, the crypto itself is hard, but the total lack of dependency makes it pathologically easy to write in a portable way. Yet C still makes it hard.