The C FAQ has some examples of historical machines with non-0 NULL representations.
From The C FAQ List, question 5.17:
Answer from janks on Stack OverflowQ: Seriously, have any actual machines really used nonzero null pointers, or different representations for pointers to different types?
A: The Prime 50 series used segment 07777, offset 0 for the null pointer, at least for PL/I. Later models used segment 0, offset 0 for null pointers in C, necessitating new instructions such as TCNP (Test C Null Pointer), evidently as a sop to [footnote] all the extant poorly-written C code which made incorrect assumptions. Older, word-addressed Prime machines were also notorious for requiring larger byte pointers (
char *'s) than word pointers (int *'s).The Eclipse MV series from Data General has three architecturally supported pointer formats (word, byte, and bit pointers), two of which are used by C compilers: byte pointers for
char *andvoid *, and word pointers for everything else. For historical reasons during the evolution of the 32-bit MV line from the 16-bit Nova line, word pointers and byte pointers had the offset, indirection, and ring protection bits in different places in the word. Passing a mismatched pointer format to a function resulted in protection faults. Eventually, the MV C compiler added many compatibility options to try to deal with code that had pointer type mismatch errors.Some Honeywell-Bull mainframes use the bit pattern 06000 for (internal) null pointers.
The CDC Cyber 180 Series has 48-bit pointers consisting of a ring, segment, and offset. Most users (in ring 11) have null pointers of 0xB00000000000. It was common on old CDC ones-complement machines to use an all-one-bits word as a special flag for all kinds of data, including invalid addresses.
The old HP 3000 series uses a different addressing scheme for byte addresses than for word addresses; like several of the machines above it therefore uses different representations for
char *andvoid *pointers than for other pointers.The Symbolics Lisp Machine, a tagged architecture, does not even have conventional numeric pointers; it uses the pair
<NIL, 0>(basically a nonexistent<object, offset>handle) as a C null pointer.Depending on the "memory model" in use, 8086-family processors (PC compatibles) may use 16-bit data pointers and 32-bit function pointers, or vice versa.
Some 64-bit Cray machines represent
int *in the lower 48 bits of a word;char *additionally uses some of the upper 16 bits to indicate a byte address within a word.
c - When was the NULL macro not 0? - Stack Overflow
c - Correct way of defining NULL and NULL_POINTER? - Stack Overflow
struct - Passing NULL as argument to a C macro - Stack Overflow
linux - Meaning of NULL definition for a macro in C - Stack Overflow
Videos
Hey, so when I was reading my gf's script for C classes, the tutor has written that
In some compilers there is no NULL but it can be easily replaced by a short macro
#define NULL 0
Does any compiler exist, which actually does not recognize NULL?
The C FAQ has some examples of historical machines with non-0 NULL representations.
From The C FAQ List, question 5.17:
Q: Seriously, have any actual machines really used nonzero null pointers, or different representations for pointers to different types?
A: The Prime 50 series used segment 07777, offset 0 for the null pointer, at least for PL/I. Later models used segment 0, offset 0 for null pointers in C, necessitating new instructions such as TCNP (Test C Null Pointer), evidently as a sop to [footnote] all the extant poorly-written C code which made incorrect assumptions. Older, word-addressed Prime machines were also notorious for requiring larger byte pointers (
char *'s) than word pointers (int *'s).The Eclipse MV series from Data General has three architecturally supported pointer formats (word, byte, and bit pointers), two of which are used by C compilers: byte pointers for
char *andvoid *, and word pointers for everything else. For historical reasons during the evolution of the 32-bit MV line from the 16-bit Nova line, word pointers and byte pointers had the offset, indirection, and ring protection bits in different places in the word. Passing a mismatched pointer format to a function resulted in protection faults. Eventually, the MV C compiler added many compatibility options to try to deal with code that had pointer type mismatch errors.Some Honeywell-Bull mainframes use the bit pattern 06000 for (internal) null pointers.
The CDC Cyber 180 Series has 48-bit pointers consisting of a ring, segment, and offset. Most users (in ring 11) have null pointers of 0xB00000000000. It was common on old CDC ones-complement machines to use an all-one-bits word as a special flag for all kinds of data, including invalid addresses.
The old HP 3000 series uses a different addressing scheme for byte addresses than for word addresses; like several of the machines above it therefore uses different representations for
char *andvoid *pointers than for other pointers.The Symbolics Lisp Machine, a tagged architecture, does not even have conventional numeric pointers; it uses the pair
<NIL, 0>(basically a nonexistent<object, offset>handle) as a C null pointer.Depending on the "memory model" in use, 8086-family processors (PC compatibles) may use 16-bit data pointers and 32-bit function pointers, or vice versa.
Some 64-bit Cray machines represent
int *in the lower 48 bits of a word;char *additionally uses some of the upper 16 bits to indicate a byte address within a word.
There was a time long ago when it was typed as ((void*)0) or some other machine-specific manner, where that machine didn't use the all-zero bit pattern.
Some platforms (certain CDC or Honeywell machines) had a different bit pattern for NULL (ie, not all zeros) although ISO/ANSI fixed that before C90 was ratified, by specifying that 0 was the correct NULL pointer in the source code, regardless of the underlying bit pattern. From C11 6.3.2.3 Pointers /4 (though, as mentioned, this wording goes all the way back to C90):
An integer constant expression with the value
0, or such an expression cast to typevoid *, is called a null pointer constant.
Macro expansion is just text replacement, so when you passed NULL, it will expand to NULL->member, clearly it is an error. One way is to use a temporary variable for that:
#define macro1(arg1) \
do{ \
A* p = (arg1);
int _state = 0; \
if (p && p->member_) \
_state = p->member_->state_; \
printf("%d", _state); \
} while(0)
A *a = new A():
macro1(a);
macro1(NULL);
This way both cases will work.
You have to understand what's a macro in order to understand your mistake. Except for the compiler, there's an animal called pre-compiler. It replaces all the macros' references by the actual code defined for this macro. So this code:
#define macro1(arg1) \
do{ \
int _state = 0; \
if (arg1 && arg1->member_) \
_state = arg1->member_->state_; \
printf("%d", _state); \
} while(0)
A *a = new A():
macro1(a); // Works
macro1(NULL); // Error
will be replaced with:
A *a = new A():
do{
int _state = 0;
if (a && a->member_)
_state = a->member_->state_;
printf("%d", _state);
} while(0)
do{
int _state = 0;
if (NULL && NULL->member_)
_state = NULL->member_->state_;
printf("%d", _state);
} while(0)
THIS code will be compiled. And now you can see for yourself what's the root cause of the compilation error.
There's probably some subtle reason that could be used to justify the difference, but I expect there's just no demand for a macro for the zero character (named NUL in ASCII).
NULL can be any null pointer constant of the implementation's choosing, it might be an integer or a pointer. So it's used when you need a null pointer constant, but you don't want to give readers the impression that it matters exactly which null pointer constant is used. Basically, you can use it to initialize/assign a pointer, when you want it to be a null pointer. You can also use it when testing whether a pointer is null.
Not everyone bothers using NULL. 0 is a permitted value of NULL, so anywhere that you can portably use NULL, you can use 0 instead. Personally I don't think the C language would be any poorer if it didn't have NULL. In particular, you can get into trouble passing NULL in a varargs function, because people generally think it's a pointer, and it converts to a pointer if required, but varargs functions don't convert it. So if it's not a pointer where a pointer is needed then you get UB. The same applies to functions without a prototype in scope.
The macro you propose would always expand to an int with value 0. "We need more ways of writing zero" doesn't seem to me the kind of thing that would excite the C standard committee to take action, even if it was proposed. If there was a set of macros naming the characters in the basic execution set, then having one for NUL in that set would make sense. But people seem happy to write 'a' instead of LATIN_LOWERCASE_A, '\n' instead of LINEFEED, and 0 or '\0' instead of NUL.
A macro has to expand to something that is not a macro. It's not turtles all the way down.
All are out of date.
Use nullptr instead. That's a special value for a pointer that doesn't point to anything. It even has its own type, std::nullptr_t.
There's no guarantee that the address 0x0 corresponds to the "uninitialised" pointer value, but note that the literal 0 is guaranteed to convert to the null pointer value.
If you are using C++11 then its advisable to use nullptr instead of NULL.
Below are a few lines from The C++ Programming Language by Bjarne Stroustrup
- In older code, 0 or NULL is typically used instead of nullptr (§7.2.2). However, using nullptr eliminates potential confusion between integers (such as 0 or NULL) and pointers (such as nullptr).
- there are differences in the definition of NULL in different implementations; for example, NULL might be 0 or 0L. In C, NULL is typically (void∗)0, which makes it illegal in C++ (§7.2.1):
- Using nullptr makes code more readable than alternatives and avoids potential confusion when a function is overloaded to accept either a pointer or an integer
I hope this will help you to understand.