When was the NULL macro not 0?
I vaguely remember reading about this a couple of years ago, but I can't find any reference on the net.
Can you give me an example where the NULL macro didn't expand to 0?
Edit for clarity: Today it expands to either ((void *)0)
, (0)
, or (0L)
. However, there were arc开发者_如何学Pythonhitectures long forgotten where this wasn't true, and NULL expanded to a different address. Something like
#ifdef UNIVAC
#define NULL (0xffff)
#endif
I'm looking for an example of such a machine.
Update to address the issues:
I didn't mean this question in the context of current standards, or to upset people with my incorrect terminology. However, my assumptions were confirmed by the accepted answer:
Later models used [blah], evidently as a sop to all the extant poorly-written C code which made incorrect assumptions.
For a discussion about null pointers in the current standard, see this question.
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.
In C compilers, it can expand to '((void *)0)
' (but does not have to do so). This does not work for C++ compilers.
See also the C FAQ which has a whole chapter on null pointers.
In the GNU libio.h file:
#ifndef NULL
# if defined __GNUG__ && \
(__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8))
# define NULL (__null)
# else
# if !defined(__cplusplus)
# define NULL ((void*)0)
# else
# define NULL (0)
# endif
# endif
#endif
Note the conditional compilation on __cplusplus. C++ can't use ((void*) 0) because of its stricter rules about pointer casting; the standard requires NULL to be 0. C allows other definitions of NULL.
C compilers usually use ((void *)0)
. The reason is passing NULL
to functions with variable arguments (or now rare but still legal functions without prototype). When pointers are larger than int, 0
will only be promoted to int
and will thus not read correctly as pointer.
C++ compilers can't use that definition because C++ does not permit implicit cast from void *
(casting 0
to any pointer is special-cased). However C++11 introduced new keyword nullptr
that is a null pointer constant of special nullptr_t
type implicitly convertible to any pointer type, but not number. This solves both the variadic argument problem and the implicit cast and additionally more severe problems with overload selection (0
for obvious reason selects int
overload over pointer one). It is legal to define these yourself for older compilers and some C++ compilers tried that in the past.
In modern C, void *pointer = 0;
is meant to initialize "pointer" to not point at anything. It is platform-specific as to whether that is accomplished by setting the bits of "pointer" to all-zero.
In the past, this formal meaning of "0" in a pointer context was not established. It was necessary to set the pointer to the actual value that the platform treated as "doesn't point anywhere". As an example, a platform might choose some fixed address that never gets a page mapped to it. In this case, in an old compiler, the platform might have defined NULL
as:
#define NULL ((void*)0xFFFFF000)
Of course, today, there's no reason not to define it as ((void*)0)
.
NULL
macro in C expands to implementation defined null-pointer constant. It can be anything (since it is implementation-defined), but in pointer context the effect is always the same as if it expanded to constant 0
.
There has never been a time in standard C history when NULL
expanded to something specifically not 0
, unless you consider (void *) 0
as "not 0". But (void *) 0
for NULL
is widely used to this day.
精彩评论