5 ms·
The language would be more complex if it tried to specify all behavior. UB allows it to be simpler. Do not confuse simple with easy.
by clarry 7y ago
The language would be more complex if it tried to specify all behavior. UB allows it to be simpler.
Do not confuse simple with easy.
- loup-vaillant 7y agoI disagree. Much UB makes the language less orthogonal, and ultimately makes the specification longer. And more complex. Classic example of illusion of simplicity: signed integer overflow. Going outside the range? Undefined. Left shifting a negative integer? undefined. You can't just assume 2's complement, you have to be aware of the Nasal Demons compilers are becoming increasingly apt at summoning. Another one: out of bounds array index is undefined, even if there was something of the right type there: #include <stdio.h> #include <stdint.h> typedef struct { uint64_t a[8]; } blk; int main() { blk b[2]; for (size_t i = 0; i < 16; i++) { b[0].a[i] = i; } printf("normal : %lu\n", b[1].a[2]); printf("overflow: %lu\n", b[0].a[10]); // undefined return 0; } Depending on optimisations and compilers, you won't get the same result every time, and all sanitizers warn you about the UB. Conclusion: you can't assume a flat memory model, even on modern X86 in 32 or 64 bit mode. There are exceptions, depending on how you access memory, through which pointer. Again, your language specs just got longer for not defining what happens outside the bounds. --- Undefined behaviour simplified one thing: the compilers themselves. At least, until they got the idea to use UB for optimisation, and started to summon the Nasal Demons as a result.
- clarry 7y ago> Classic example of illusion of simplicity: signed integer overflow. Going outside the range? Undefined. Left shifting a negative integer? undefined. You can't just assume 2's complement, you have to be aware of the Nasal Demons compilers are becoming increasingly apt at summoning. I don't see this as an example of illusion of simplicity. On the contrary. Specifying exactly what must happen on integer overflow or left shift is almost always going to require more than saying "the behavior is undefined." Especially so if you try to keep it portable and allow different behaviors that are natural for different systems. And it probably won't help you if you're trying to write portable code, because now you need to worry about all the defined behavior that might not be what you want. If you want portable code, then you shouldn't be able to assume a specific behavior, and if you want a language standard that enables the language to perform well across dislike platforms, you can't dictate specific behavior. If you don't care about portability, you're free to enter a contract between you and your platform & compiler. For example, use -fwrapv. The standard doesn't forbid such a thing. By keeping things simple and leaving them undefined, it explicitly enables you to do things like this without violating what's defined in the standard. > Another one: out of bounds array index is undefined, even if there was something of the right type there: That's a very specific scenario. In a lot of cases, there are arrays whose indexes are not right there, not constants (would require range analysis), or array size is not known (but maybe could be inferred). So the standard would have to become more complex in order to specify that something specific must happen in the special case that you're indexing into an array of known size in scope, with a constant index. By simply making out of bounds access undefined, they cover all cases of array access in one short sentence. Luckily, again, since the standard doesn't require any specific behavior, your friendly implementation is free to invent its own specific behavior, for example issue a diagnostic and return an error code because it saw that you were indexing out of bounds. > Again, your language specs just got longer for not defining what happens outside the bounds. Why don't you show how to make the spec shorter by defining exactly what happens in this specific case? Remember that the spec still has to cover other cases, so you can't just delete all the text. You have to add a new case! Post a diff.
- loup-vaillant 7y agoIntegers represent the binary field of integers modulo 2^b, where b is the width of the integer. For signed integers, any value v >= 2^(b-1) is interpreted as being v-2^b instead. That's how you specify "exactly what must happen on integer overflow". You thought I would have to take every undefined special case one by one and define them? That's naïve. A similar argument can be made for pointers: pointers are integers that represent memory addresses. Dereferencing a pointer to an allocated region gives you the value stored in that region (insert casts, uninitialised values, trap representations etc here). Dereferencing a pointer to a region that is not currently allocated is undefined. And voilà, we no longer care how a pointer was constructed then dereferenced, we only care about the address, and whether it pointed to an allocated region (and initialised with a compatible type etc, though that part could also be simplified). > That's a very specific scenario. I'm currently reporting a bug in the Libsodium cryptographic library because its Scrypt implementation (password hash by Colin Percival) has precisely this bug. My guess is that Colin was a little too clever there, and treated C as a lower level language than it actually is. A very specific scenario that may be, but it's a scenario we do encounter in real life.
- clarry 7y ago> That's how you specify "exactly what must happen on integer overflow". So yes, like I said, you can force a particular behavior, and throw out any system for which that doesn't come naturally - you just made the language more complex for them. And no, that is not simpler than "the behavior is undefined", just different (and more imposing). > And voilà, we no longer care how a pointer was constructed then dereferenced, we only care about the address, and whether it pointed to an allocated region (and initialised with a compatible type etc, though that part could also be simplified). Eh. You did nothing about undefined behavior. It's still there. > A very specific scenario that may be, but it's a scenario we do encounter in real life. So what was your proposal that doesn't make the language more complex? Because above, you just gave us undefined behavior, which is our starting square.
- loup-vaillant 7y ago