The large areas of undefined and unspecified behavior has always been an issue in C. It was somewhat acceptable when you were using it as a direct replacement for assembler,
but Java and many of other follow-ons endevaored to be more portable/rigourous. Of course, you can write crap code in any language.
It didn’t take modern C to do this. On the PDP-11 (at least not in split I/D mode), location zero for example contained a few assembler instructions (p&P6) which you could print out.
Split I/D and VAX implementations made this even worse by putting a 0 at location 0. When we moved from the VAX to other processors we had location zero unmapped. For the
first time, accessing a null pointer ended up trapping rather than either resulting in a null (or some random data). Eventually, we added a feature to the kernel called “Braindamanged
Vax compatibility Mode” that restored the zero to location zero. This was enabled by a field we could poke into the a.out header because this was needed on things we didn’t have
source code to (things we did we just fixed).
Similar nonsense we found where the order that function args are evaluated was relied upon. The PDP-11, etc… evaluated them right-to-left because that’s how they had to push them
on the stack for the call linkage. We had one machine that did that in the opposite order (I considered flipping the compiler behavior anyhow0 and when we got to the RISC architectures,
things were passed in registered so the evaluation was less predictable.
I already detailed the unportability problem I found where the BSD kernel “converted by union”.
The most amusing thing I’d have to say was that one day I got a knock on my office door. One of the sales guys from our sister company wanted to know if I could write some Novell
drivers for an encrypting ethernet card they were selling. The documentation for writing the driver was quite detailed but all describing i386 assembler interfaces (and the examples
were in assembler). About a week into the project I came to realization that the linkages were all the C subroutine calls for that platform. The caller was C and there was no particular
reason why the driver wasn’t also written in C.