This is, and continues to be, an incredibly useful feature that makes C and C structs immensely useful concepts. Part of that does need an invalid value[1]. NULL is convenient for this and although there are some very weird JavaScript-trinity-meme-style consequences for this[2], it's such a useful concept that basically all languages that have the ability to construct pointers have a null pointer[3].
The alternative world looks like everyone inventing their own invalid values. Invalid, non-null, pointers are typically MUCH worse than null pointers for debuggability and security. If you unintentionally read/write/execute memory at 0x0 (by far the most common value for NULL), most operating systems will trap this, whereas may not necessarily if 0x12345678 is your invalid value.
[1]: Stuff like IA64 had NaT bits which were effectively an extra bit for what I assume to be this sorta thing. The problem with this is that it costs an extra bit. I don't really know much about IA64, but presumably [NaT 1] + [don't care] would be your null pointers here. I think?
[2]: Really what the standard, in my opinion, should have done is probably not make use of the null pointer UB for many different functions. A lot of compilers took the UB surrounding that to make incredibly dubious "optimizations" that broke stuff with zero actual performance benefit whatsoever
[3]: Yes, even Rust. Although some (again in my opinion) unfortunate design decisions made it so that C-Rust FFI isn't zero cost because of how it treats spans/slices
If Rust slices already make you sad, then the thing I'm cooking up will make you cry for days.
C pretends types exist with you, but once bytes hit the road, it's all real-life and segmentation faults.
Issue with types and C is while the compiler knows about them the standards committees don't want you to be able to. If C had first class types more people would abandon C++ and that can't be allowed to happen.
A similar limitation exists when peforming accesses against the cache, but at a much finer granularity.
For bytes, the alignment restriction obviously exists in the 8 bit level. You have one output byte and 64 multiplexer inputs.
If you scale this up to 8 bytes, you will need a lot of 64 Input multiplexers.
But even if you can take the silicon area hit, there is the problem of crossing cache lines and pages.
In the end, you cannot divide memory into blocks and allow primitives to cross those blocks without requesting both blocks at the same time. That's an inefficient waste of resources so why support the wasteful usecase in the first place?
To be honest, I've never seen much indication that the C and C++ committees are particularly fond of each other. They sometimes coordinate, but they're mostly content letting each other evolve in different directions. C is the way it is only after a long process of evolution away from the bits and bytes of BCPL into the strictly typed language we got from ASNI.
Though I should note that in a way, even some ISAs have one, what with e.g. separate float vs integer registers.
The historical argument and appeal to assembly is illogical here. The only real argument is that niche value optimization is too complex or too clever for the time so even if sum types were in C, nullable pointers would still exist either way.
You're telling me OCaml / Rust / Haskell compile to fairy pixie dust? Obviously their compilers figured it out and it works.
If that was the goal, it failed horribly - the gatekeeping didn't work because the popularity exploded.
> You're telling me OCaml / Rust / Haskell compile to fairy pixie dust? Obviously their compilers figured it out and it works.
I said nothing of the sort.
No, I didn't - I asked how sum types were supposed to work in an era of 64KB memory systems.
The boring cases require an enum tag in C too.
By bringing up the one thing that doesn't matter, your argument becomes purely ideological.
And which modern C compiler fits into 64KB? Even TCC needs 100KB. But that's beside the point. No machine of the last 36 (I'll push my chances, 40) years needs to fit a compiler in 64KB.
That's famously a single-pass compiler. Rust is famously unable to compile in a single pass.
It is not possible to make a borrow-checking language that compiles in a single pass.
> No machine of the last 36 (I'll push my chances, 40) years needs to fit a compiler in 64KB.
Exactly - that's why C is what it is: it wasn't a mistake, they were working under the constraints of the time. My original comment (that you appeared to disagree with) said specifically "Remember where C came from and why it was designed the way it was."
Let me ELI5 it for you: It was specifically designed to emit assembly in a single pass because of the constraints of the time.
WTF does "Hur Dur Rust Goodest!" comments mean in this context?
I probably should have replied under the other comment. I was also referring to your
> No, I didn't - I asked how sum types were supposed to work in an era of 64KB memory systems.
But context got lost between replies.
> that's why C is what it is
C famously had a big redesign in 1990. The language of today isn't the same K&R printed.
Apparently they found a way to have the CPU know... about "this stuff".
I don't know what to tell you, but you're clearly not cut out to be a software developer in either machine code, assembly or C or any other language if you don't understand something this basic.
Sum types aren't the be all end all to all issues, for example you can not representer pointer values efficiently with sum types. Even rust does not wrap up pointers with sum types. Now try to go back 37 years to C89 and ask yourself if they were going to require compilers to have stringent checks like the rust compiler does.
And, tagged unions are a thing and were a thing for a long time.
Of course it's too late to change all this today; it would have been too late even 20 years ago. But outside of f.ex. Linux kernel and some other super hardcore C libraries, a lot can be done for the world to migrate to safer constructs and away from sentinel values. And that's what languages like Rust do.
Super memory constrained environments have not been the mainstream programming work for decades and now remain limited to embedded / IoT. Not sure what the reservation against sum types is these days.
No one here is saying C is a great design, but in the context of 60 years ago, it worked out pretty well, and all the language which had additional runtime complexity (Pascal of course, but also Ada and FORTH) struggled because they didn't dial the right level of complexity
You basically end up with null/0 don’t you?
So yes, it's nulls underneath, but the developer never has to think about them.
Seeking performance we've been very prone to avoid abstractions and over and over again have shown why we need the safe abstractions.
> use the type system to help us use special values safely
... but this is not the place to explain what a type system is or what sum types/maybe/optional/etc. are.
There is a huge gap between developer expectation "it's pointing at something known" and hard reality confirmed by zillions of CVE. That's the reason optionality is prevalent in modern languages and type checkers (python, typescript), nowdays even Java has sane non-nullable types.
I wouldn't call "cause of bugs and security issues" "no cost".
> it's quite rare for a pointer in C to have the ability to be NULL
As a C programmer for more than 25 years, that is the exact opposite of my experience.
In practice really 4 because "indeterminate" is a reasonable error condition you'd like to know about.
And it keeps increasing anyway: e.g. not set has subcategories: not set due to lack of user input, not set because we're loading state from the backend etc.
NULL is the first expression of that basic problem: it's definitely not enough to eliminate NULL because the first thing which happens is your non pointer default value takes it's place.