printf("The argv pointer = %d\n", argv);
printf("The argv pointer = %p\n", (void *) argv);
Edit1: Oh, and I realize it "worked" for the author, no nasal demons  appeared and the code printed a number. That's just being lucky, and it's not a very nice foundation to base an argument on.
Edit2: Okay, now I read a little bit further and I'm pretty sure the author lives in the "pointers are just integers, and NULL is zero" world, which is of course not a world that co-exists with our own.
There is no guarantee that the in-memory representation of a NULL pointer is "all bits zero", that is implementation-defined and completely up the the architecture you're running on (and the compiler).
I realize that in practice, on common hardware, NULL is typically implemented as all-bits-zero, but that is not defined by the language. I don't even know if I make sense, obviously I'm a C buff and take these things rather seriously.
The "our own" world you are describing has recently been created by UB-exploiting compilers; it's a very strange and unintuitive place, is only described in inscrutable standards documents, and compilers don't properly warn you if you do something wrong even if you ask them to. A bit more understanding of people making mistakes seems appropriate.
And the standards in question are actually have very clear and simple language disallowing those shenanigans.
"Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message)."
Alas, that language was made non-binding and now the people who made it non-binding pretend it doesn't exist at all.
Optimizing compilers don't intentionally break programs with UB, they just ignore the possibility... with very unpredictable results.
And neither is "optimising assuming UB never happens", especially if said UB is explicitly written in the code, thus contradicting the assumption.
So the compiler simply ignores this possibility and optimizes assuming the input is a valid C program.
Unless you use the sleight of hand that is commonly used in vastly expanding the meaning of the word "valid" to mean "the compiler can assume there is no UB".
Which, again, isn't "ignoring the situation".
You'd probably assume it's a prank, and treat it as a green light by proceeding normally. Compiler code gen around UB is no more complex than this, though the consequences can cause complex chains of reasoning in later optimization passes.
That's not what UB is. UB is "the standard doesn't say anything about this particular situation, because we intentionally left the standard incomplete". It never said "UB cannot happen".
But even assuming your "purple streetlight" analogy, it exactly does not cover the "complex chains of reasoning" that the compilers then do.
I saw a purple streetlight, therefore I can just assume it is green. Nope.
I saw a purple streetlight, therefore I can now run over pedestrians. Nope.
I saw a purple streetlight, therefore this now an airport runway. Nope.
The compiler-writers' justifications don't pass even the most minimal test for coherence.
I always wondered if all the C codebases that do "if (somepointer)" to check for NULL were actually wrong of if the standard guaranteed that NULL always evaluates to false even if it's not implemented as zero. Apparently it's valid and everything works even in a theoretical architecture where NULL was not implemented as 0:
So, (almost?) every expression in C has an implied boolean value, and the implied boolean value of pointers is false if they're NULL or true otherwise.
A better design is to have a boolean type, and require that the if condition tests only actual booleans, but this requires a substantial eco-system wide commitment, if you just insist on actual booleans you make programming needlessly tiresome and programmers say this language is bad because I have to write so much boilerplate for no gain. You need to actually provide the rest of the language features that make this pain worth it.
That commitment seems relatively affordable in 2021, but it was probably too big an ask when C was invented and by the time C was standardised it's too late. Likewise for C++.
It was common knowledge among C programmers long before then, for both security and portability reasons but optimization was also done. Old MacOS even used the higher 8-bits of pointers for MM flags. Other popular systems did equally weird things (think VAX.) That discussion linked to by the Jargon file is from 1992.
No. Here's an LWN discussion of a CERT advisory in 2008 trying this nonsense, claiming it's a problem that GCC 4.2 elides futile late NULL checks. https://lwn.net/Articles/278137/
In fact several other compilers already had this optimisation, and CERT had to go back and revise the advisory because in practice all that's going on is, as now Real Programmers think their undefined program ought to do whatever it was they intended, and that's not how programming works.
tcc was created in 2001 as a specifically non-optimising compiler.
Actually you can. You probably shouldn't, particularly now that many 64 bit C machine models have different sized pointers and ints. If they were going to use an int conversion, they should have used %ld.
> You should use %p which is for printing pointers,
> [no nasal daemons] That's just being lucky,
Actually it isn't "just lucky". Well, to be precise it wasn't just lucky back when C compiler writers didn't stretch the idea of UB way beyond the breaking point, and way beyond certainly the intent and arguably the letter of the standard.
(The cop out is that the part of the standard that explicitly says all these shenanigans are not OK was made non-binding in later versions of the standard, it originally was binding, and thus people pretend that this part of the standard doesn't exist at all. But it does.)
> There is no guarantee that the in-memory representation of a NULL pointer is "all bits zero",
There is no guarantee of this in the C standard. However, the C standard is intentionally incomplete, i.e. conformance to the standard is not enough for an implementation to be "fit for purpose". I think it even used to say that somewhere in the standard.
> I realize that in practice, on common hardware, NULL is typically implemented as all-bits-zero,
For a very not to say overwhelmingly large value of "typically". I can't name a single implementation that does not, and my guess would be that total market share of such implementations would be less than 0.1%, and I believe I am being very conservative with that estimate. Which also means that if you are on such an architecture, you almost certainly know it.
> but that is not defined by the language.
...because the language definition is intentionally incomplete. People talk about the "C abstract" machine as if it were a thing. It's not. C maps to real hardware, that's the point of the language, and so taking the real-world behavior of the hardware that you are going to run on into account is perfectly fine.
Your C compiler is at liberty to use a different value than 0 for the actual NULL pointer stored in memory or in a register. It has to pretend to you that it is 0, but could be 0xDEADBEEF in reality.
Before ANSI C, NULL was #define-d as the actual bit value required. In modern C NULL is always 0.
In all the modern architectures I can think of a NULL pointer is represented by a value with all bits 0, but there are lots of examples of older architectures where this wasn't true.
There is a section in the comp.lang.c FAQ about this:
At least in this case the standard goes to some length to make sure you don't have to care.
The C standard means you don't have to care that there might actually be 2^8 NULL pointers since you're running on ARM with Top Byte Ignore in use.
I learned this fact when at least one library (OpenSSL? Kerberos?) shipped with the OS relied on the non-crashing behavior, unconditionally dereferencing an optional pointer-to-struct argument.
That was a fun debugging session.