4 ms·
> The old C compilers did not do that, ever, because K&R's "behaviour is undefined" did not give that sort of licence. This extrapolation of yours is not warra
by pif 2y ago
> The old C compilers did not do that, ever, because K&R's "behaviour is undefined" did not give that sort of licence.
This extrapolation of yours is not warranted! The first compilers were simpler, and optimizations came later. The fact that people got used to simple compilers does not make their code correct with respect to the standard, sorry!
- rstuart4133 2y ago> make their code correct with respect to the standard, The point I'm making is about the standard. Somehow, instead of "undefined behaviour" (using your definition) generating a compile time error, we drifted into letting it mean compile time non-determinism. By compile time non-determinism, I mean we let the compiler generates machine instructions that don't reflect the code. I've spent a good chunk of my life battling problems introduced by non-determinism. Giving compilers a licence to generate more of it strikes me as pure insanity. And now you come along, and say the problem isn't the standard, it's the programmers not being language lawyers. It's their fault for not memorising a standard so complex it literally gives compilers permission to base their decision on whether it can generate non-deterministic code on a undecidable puzzle - the halting problem. ffs. To anchor the conversation back to the original point about C not being assembler, I want all languages I use to be glorified assemblers. Which is to say I want the way the code is translated to the next level down to be clear and transparent. Granted, clever optimisers it something clever and faster, but that those clever and faster instructions should perfectly emulate what the clear and transparent translation would have done. To the compiler writers throwing their hands up, protesting that limits them too much: this is literally what the hardware does now. It takes a stream of instructions and mangles it by compiling to micro ops, reordering them, executing them in parallel, makes guesses about branch directions and speculatively executing code, hell it sometimes even executes both the if and else sides of a conditional. It manages to extract amazing speed out of the serial instruction stream, sometimes executing 10 in parallel, and yet it manages to preserve the exact meaning of all the original instructions, even those defined 40 years ago by the 8086. I imagine the response will be "but hardware has options we don't, we need more flexibility to get the speed". It would be a fair point. But their solution to that dilemma was not so fair: twist the standard by redefining terms like "undefined behaviour" so they could get the flexibility they needed. In doing so they turned the standard maze of hidden "undefined behaviour" foot guns. The standards committee seems to think their primary job is producing something for the compiler writers. It isn't. Their primary job is to define a language programmers used to create complex, reliable computer systems. A standard full of foot guns isn't conducive to that.
- pif 2y ago> Somehow, instead of "undefined behaviour" (using your definition) generating a compile time error, I'm sorry, but this sentence shows how little you understand about undefined behaviour. Undefined behaviour cannot generate compile time errors in the general case, as the compiler does not have all the information. The only thing the compiler does is assume you wrote your program correctly, which you seem to refuse to accept as a responsibility of yours. As an example, a simple statement like "int a = b + c;" cannot cause a compile-time error about integer overflow, because the values of b and c are unknown. The hardware will know them at execution time, but the compiler does not. And as soon as you state your expectation that overflow mimic the underlying architecture, you are wrong again because you choose the wrong tool: unless you are developing a compiler, you are not allowed to use "C" and "architecture" in the same sentence. > And now you come along, and say the problem isn't the standard, it's the programmers not being language lawyers. You said it a bit more bluntly that I would, but you are right. The problem lies on the developer's side. Mind you, I found myself making this error several times; I'm not pretending to be a master programmer, not at all! But every time the only solution was to accept responsibility and modify my code accordingly. Unless you accept that your expectations are wrong, you are condemned to live the same stress over and over and over! Good luck to you!
- rstuart4133 2y ago> As an example, a simple statement like "int a = b + c;" cannot cause a compile-time error about integer overflow, because the values of b and c are unknown. Then there is no problem, because to use your terminology what happens is implementation defined. The compiler will emit the appropriate instructions and I will get whatever the hardware gives me. That is the K&R behaviour I said I was happy with. The problem happens precisely under the conditions you side stepped: when the compiler does know the behaviour is undefined. The standard says the runtime can assume that undefined behaviour never happens; therefore, the compiler can do whatever it pleases should the UB condition arise. If the compiler takes that option it generally does whatever is fastest, which is typically to delete the code. Consider this example: if (buffer + len >= buffer_end || buffer + len < buffer) loud_screaming_panic("len is out of range\n"); The second part of that condition can be true if len is negative or huge (beyond the end of the array), but adding a negative number to a pointer and indexing beyond the end of an array both yield undefined behaviour. Ergo the compiler is free to do what it damned well pleases with that condition, and gcc chose to delete it. Unfortunately, the example is real. The buffer was a packet received from the internet, "len" was a field read from that packet. If the code accepted a garbage value in len, it would then index beyond the array and bad things would happen. The programmer put a test in there to prevent that. Gcc deleted it. A CVE was the result. The point being: gcc knew the code triggered undefined behaviour. I guess there are three options when you see code like that: do what it did with your "int a = b + c;" example and just emit the instructions (which is what it would do with -O0), or print an error and refuse to compile the code because it has UB, or merrily do whatever it damned well pleased without so much as a warning and continue on its merry way. The first option is K&R behaviour. The second is very Rust like. Either of those would have been fine in this case. The third option lead to disaster, and I think the standard allowing it is inexplicable. And now we get to the point where I have to say it: I don't think you know as much about C as you think you do. The C standard defines a lot of undefined behaviour, so much it would be near impossible to avoid it even if you wanted to. Worse as it happens, C programmers very deliberately don't avoid it. Instead they exploit it by assuming K&R behaviour. Take your illustration as an example, "int a = b + c;". You conveniently assumed the compiler knew nothing about "b" and "c", but lets say we had some if tests above that constrained "b" and "c" values so that "b + c" must overflow. In that case the standard says a is undefined, and it can throw everything that depends on it's value away. But as a C programmer you must know C programmers regularly rely on the fact that integer overflow yields exactly the same result on all C implementations. For example, if we are calculating a checksum of a buffer modulo 256, every C programmer will just "uint8_t checksum" and sum every byte into it. But if you are right about overflow being UB (and you are), then every C compiler could put a check to see if the next add did overflow "checksum", and stop the loop immediately if so. There is no need to tell the programmer about this because it is UB, and the compiler can do whatever it damned well pleases in the name of speed. No compiler does particular optimisation of course. Not because the standard doesn't allow it, but because they would be lynched. Unfortunately the threat of lynching didn't stop them from deleting the code above. > Unless you accept that your expectations are wrong, you are condemned to live the same stress over and over and over! Good luck to you! I didn't say you were wrong about what the standard says. I'm saying the stance taken by the standard on how undefined behaviour can be handled is insane. This attitude taken by the committee is one of the factors leading to government agencies saying C should be dropped. I am also saying you are wrong when you claim the compiler could not emit an error when it classified something as UB, and then deleted code or whatever. I think that is self evident from the points I made above.