4 ms·
It's completely valid. It's just not conforming. No the compiler is not entitled to insert a segfault. Segaults come from architectures and operating systems,
by vyodaiken 8y ago
It's completely valid. It's just not conforming.
No the compiler is not entitled to insert a segfault. Segaults come from architectures and operating systems, not from compilers. The compiler is free to leave in the dereference of the null pointer and then, who knows what happens. But the compiler can't break the code by deleting a perfectly conforming null check on the theory that a previous dereference means that the pointer is not null.
The original Bourne shell used signal to catch memory faults so it could operate dynamic paging! If your OS/implementation permits, C is supposed to allow you do do things like that. The compiler is NOT supposed to insulate the programmer from bumps. (Disclaimer: I am not endorsing the horrible design of the Bourne shell which was also written in a macro style that made C into pseudo Algol).
- palotasb 8y agoWhat is your definition of valid? It's not valid by any C standard or common compiler implementation. For the sake of any argument, we should at least agree on a common definition for valid. As for deleting null checks, time-travelling UB etc. the compiler always assumes the code is valid, bug-free for the compiler-specific definition of valid. This is at least the standard definition but it can be expanded by flags and extensions. Then they optimize that code without regard to what happens if the code actually had programming bugs.
- vyodaiken 8y agois k = i+j valid C? Can it be translated sensibly to add i,j; jump on overflow to random; store k; ? How about sometimes omitting the jump to random and sometimes not?
- geofft 8y agoSigned integer overflow is undefined, so the only obligation is that the code operates correctly when signed integer overflow doesn't happen. So, yes all those options are permissible, because as far as the C standard specifies, it's "add i,j; if false jump to random; store k". If you want to add a definition for signed integer overflow (as C++ is considering, see http://wg21.link/P0907r0 http://wg21.link/P0907r0 ) then it would be defined and the compiler would no longer get to consider "if signed overflow" and "if false" the same.
- vyodaiken 8y agoI 100% doubt that that Ritchie would have agreed. This is the existing interpretation of the standard, which is exactly what I propose to change. I believe the standard interpretation has strayed from both the language design and the objectives of the language.
- jcelerier 8y ago> Segaults come from architectures and operating systems, not from compilers. But when you are programming in C, you are programming against the C abstract machine, not against an architecture or operating system. It's this machine that you are breaking when you invoke ub.
- vyodaiken 8y agoC is not Java.
- jcelerier 8y ago... and ? it still targets an abstract machine, defined in the C standard in section 5.1.2.3 (Program execution) : http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
- vyodaiken 8y agoIt targets the abstract machine, but the dispute here is about what should happen when the program operation goes into territory that the abstract machine does not specify. And C code is EXPECTED to go beyond the territory of the abstract machine.
- geofft 8y ago> And C code is EXPECTED to go beyond the territory of the abstract machine. Passive voice! You expect C code to go beyond the territory of the abstract machine. The authors of the standard, quite clearly, do not. I can see both of these arguments. I think there is a place in the world for a C that is machine-specific. But it's clearly not what the standard authors have in mind, there are clearly people who find the genericness of C useful (cf. "portable assembler"), and piecemeal solutions like skipping individual bad optimizations won't solve the underlying problem that there's no way in C to say "Dude, I'm writing for a reasonable UNIX on a reasonable modern machine with a single flat address space for code and data and non-exception-raising two's-complement arithmetic and the ability to do nonaligned reads/writes and other normal things, please stop thinking I might also be writing for a PDP-11 bootloader." I think you should go to the standards committee with a single proposal, which is that C should stop trying to be portable to weird / old architectures (and people who need them can use weird / old compilers, anyway). Among other things, you can now enable certain types of optimizations if the compiler is permitted to assume that arithmetic is two's-complement and nonaligned reads/writes work and so forth.
- geofft 8y ago> But the compiler can't break the code by deleting a perfectly conforming null check on the theory that a previous dereference means that the pointer is not null. Why is this different from letting the compiler optimize out the second check here? int foo(unsigned int i) { if (i < 5) return this(); if (i < 4) return that(); return those(); } The C standard knows that NULL is special, a marker value for an invalid pointer. (The implementation does not have to translate it as the pointer with numeric value zero; the abstract machine just needs some value.) Absent an Optional/Maybe/etc. type, C needs some invalid pointer. What about the following—are you okay with compilers optimizing it? struct pointer { int *value; char valid; } void *deref(struct pointer p) { if (!p.valid) abort(); return *p.value; } void stuff(struct pointer p) { int n = deref(p); if (!p.valid) { fprintf(stderr, "Hey, that pointer wasn't valid!\n"); return; ... } No NULLs involved. > The original Bourne shell used signal to catch memory faults so it could operate dynamic paging! If your OS/implementation permits, C is supposed to allow you do do things like that. I'd believe that (especially given the state of optimizers in Bourne's era) C does allow you to do things like this. I'd also believe that with sufficient -fno-unwanted-cleverness flags. C is the best way to do things like this. But asking C to be designed to support things like this is quite a change. I get that this is exactly the change you're pushing for, but, the only way it can work is if the C abstract machine becomes hardware-dependent enough that you can embed platform assumptions in your C—and so C is no longer a "portable assembler." There's precedent for this. One that comes to mind is the ability to cast a data pointer to a function pointer (which doesn't have to be valid; the abstract machine doesn't know if your physical machine uses different address spaces and different representations for the two, and even the brand new architecture WebAssembly uses different address spaces, but compatible representations). dlsym() implicitly requires this, and POSIX.1-2013 finally made an explicit requirement that a C implementation conforming to POSIX must define it in a meaningful way. http://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html http://pubs.opengroup.org/onlinepubs/9699919799/functions/dl... I would certainly support a variant of C that did things like assume a flat address space, validity of all pointer values besides NULL (and an expectation that signals can catch and fix invalid pointer dereferences), char = unsigned char, 8-bit bytes, two's complement arithmetic, etc. It would not support every architecture C supports. It wouldn't be a good fit as a C standard, but as a variant for people who are happy to never target segmented real-mode x86 or the PDP-11, it seems pretty great.