Videos
If we want to "Nullify" something why dont we create a pointer in a controlled way that points to value 0?
In C, NULL is a macro that expands to a null pointer constant.
7.19p3
The macros are
NULL which expands to an implementation-defined null pointer constant; ...
A null pointer constant is an integer constant expression with the value 0 (
e.g., 0, 1-1, 42*0LL, etc.) or such an expression cast to (void*).
6.3.2.3p3
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.66) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
Most common C implementations define NULL to be 0, 0L, or ((void*)0).
So you are correct. NULL need not be a pointer.
(IIRC, C++ doesn't even allow the (void*) cast in NULL, meaning NULL in C++ always has integer type. Because of that and because void* pointers
do not compare with regular pointers so readily in C++, C++>=11 now has a special nullptr keyword.)
NULL itself is not a pointer, it is a macro that can be used to initialize a pointer to the null pointer value of its type. When compared to a pointer, it compares equal if the pointer is a null pointer and unequal if the pointer is a valid pointer to an object of its type.
There is no semantic difference between char *p = 0; and char *p = NULL; but the latter is more explicit and using NULL instead of 0 is more informative in circumstances where the other operand is not obviously a pointer or if comparing to an integer looks like a type mismatch:
CopyFILE *fp = fopen("myfile", "r");
if (fp == NULL) {
/* report the error */
}
Similarly, there is no semantical difference in C between '\0' and 0, they both are int constants. The first is the null byte, the second the null value. Using 0, '\0' and NULL wisely may seem futile but makes code more readable by other programmers and oneself too.
The confusion may come from misspelling or mishearing the null pointer as the NULL pointer. The C Standard was carefully proof read to only use null pointer and refer to NULL only as the macro NULL.
Note however that one the accepted definitions of NULL, #define NULL ((void*)0) makes NULL a null pointer to void.
Any pointer type with the value 0 is called a null pointer. Here is the explanation from the C standard ยง6.3.2.3:
- An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function
Null pointer is a pointer that points to memory address 0. Most system reserve this address so that no object will ever reside in there. This reservation allows to use it as blank address.
If you try to read or write from null pointer you will get runtime error which is sometimes called segmentation fault, or null pointer exception.
In your example the null pointer is used to indicate end of the list. So this condition (struct entry *) 0, checks if you have reached end of the list and iteration should stop
Usually it considered better form to use constant NULL instead of literal value 0. This makes code more readable, and also covers very rare case when NULL pointer is not a 0
The cast (struct entry *) is just to avoid compiler warning, because literal 0 is of type integer not a pointer. That's another reason to use constant NULL, because it is usually defined as (void*) 0 which compares nicely to any pointer value without a warning from compiler
The definition of NULL is a syntactic crutch to allow a programmer a clear expression that a pointer is, at certain times, pointing nowhere. It's meant for readability - and with increased compiler bloat even for automated checks.
On a hardware level there is no such thing. A pointer is a word, and a word always holds a valid number. So by convention zero was chosen - it could have been any other. Like -1 for example. Selecting 0 offered the advantage that a simple if(pointer) could be used to check if it's a valid pointer (or more correct, not 0).
So my question is what led some C standard to treat the NULL pointer differently from any other pointer?
C also doesn't treat NULL different from any other pointer. The C library in contrast does, but not because of NULL, but rather as its (runtime) value of 0 will make some functions fail, when used as input.
Did K&R want to target an exotic architecture or something?
No. It's a concept needed from a pure software engineering point of view. Having a syntactic construct to handle uninitialized pointers improves readability and possibly enables further checks.
Now, for the historic part, NULL is, like the related handling of TRUE/FALSE inherited from BCPL (through B). Just here it was called nil.
Even with that little historic bit, I'm not sure if Retrocomputing is the right place to ask for this, as it's about a basic concept in software engineering and not anything burdened with historic implication. So a quick search in Software Engineering and Stack Overflow shows that this question has been asked many times. It's something people always stumble on, isn't it?
The important thing you may be missing is that a null pointer in C is not required by the standard to have the same binary representation as the number zero. It is still a "normal" pointer, but it points to a special location that the program is not allowed to use.
The integer constant 0 is turned into nullptr when used as a pointer. Similarly, 0 will become 0.0 when used in floating-point calculations, or false in boolean operations. Coercions are not just one-way: if(ptr) will convert a pointer into a boolean which indicates whether the pointer is not null. All of these conversions serve to avoid requiring that a null pointer shares the same representation as zero.
Most machines represent integer zero as all-bits-zero and comparisons against that are particularly cheap, so it is worthwhile to arrange things so that sentinel values such as null pointers and end-of-string markers are all-bits-zero, and also that +0.0 and false are also all-bits-zero.
There is some subtlety in C in that a literal 0 is converted into nullptr, whereas casting an integer that happens to be zero into a pointer will produce a pointer to location zero. Because they are the same thing on modern platforms, a whole class of potential bugs go away.