views:

277

answers:

6

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 architectures 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 adress 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.

+1  A: 

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.

Jonathan Leffler
+2  A: 

There was a time long ago when it was properly typed as ((void*)0). Other than that, I have a vague recollection that some platforms had a different bit pattern for NULL (ie, not all zeros) although I think ISO/ANSI fixed that at some point by specifying that 0 was the NULL pointer regardless of the underlying bit pattern.

paxdiablo
A: 

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 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.

AndreyT
There has never been a time in _ISO_ C history ... early compilers had different bit patterns long before the 0 was codified as NULL regardless of the underlying bits. Although, given my advancing years, there's no way I could remember which ones they were :-)
paxdiablo
Chapter and verse please? The ISO C standard explicitly says that NULL can expand to whatever the implementation wants it to expand to. `4.1.5. Common Definitions <stddef.h> [...] The macros are NULL which expands to an implementation-defined null pointer constant; and [...] `. This should not be confused with other text that says that the expression 0 converted to pointer type is always a valid way to get a null pointer constant. Which has nothing to do what what NULL expands to.
janks
@janks, I think @paxdiablo is saying that in ISO C, `0` in pointer contexts is the null pointer constant, but in pre-ISO (pre-ANSI) C, this wasn't necessarily true. Presumably in those variants one would write `NULL`, or whatever magic number the null pointer happened to be.
Alok
@Alok, yeah, that's exactly what his first paragraph said. But then he contradicted it by saying that "there has never been a time in C history when NULL expanded to something specifically not 0", which is rubbish. The standard doesn't require it to, and he would have to have used every implementation of C ever, without exceptions, to prove the negative. We once believed there were no black swans because we'd only seen white ones.
janks
@janks: elsewhere in the C standard, a "null pointer constant" is defined to be either an ICE with value 0, or else one of those cast to `(void*)`. That's not "whatever the compiler wants", and I think AndreyT is right to say that any such thing is "specifically 0". The standard forbids `#define NULL ((void*)0xFFFF)`, even if 0xFFFF converts to a null pointer on that implementation. I think it's probably wrong to ignore pre-standard C, since the question is clearly about ages past, but the standard does forbid black swans.
Steve Jessop
@SteveJessop: Can you cite the text from the standard that you feel forbids `#define NULL ((void*)0xFFFF)`? The "as-if" rule says that an implementation can do whatever it pleases so long as the observable side-effects of your program match those of the abstract machine. While it would be pretty stupid, I can't see any reason why the implementation cannot define NULL in a strange way, and then "fix-up" any context where NULL is used such that it respects the semantics of the abstract machine.
janks
@janks: In n1256, the text defining a null pointer constant is 6.3.2.3/3. The text which says NULL is a #define to a null pointer constant is 7.17/3. The program which would fail the as-if rule if NULL were #defined to `((void*)0xFFFF)` is one which stringifies NULL and prints it out, for example `#define SSTRING(A) #A #define STRING(A) SSTRING(A) int main() {const char *ptr = STRING(NULL); printf("%s\n", ptr); }`. Plus some linebreaks.
Steve Jessop
Yes, the stringize argument is clever, it's the only thing that I can see that could arguably forbid a strange definition of NULL. Unless perhaps the implementation could detect when you're trying to stringize NULL, and substitute some magic. But I personally don't believe that 4.1.5 combined with 3.2.2.3 are sufficient to rule out a strange definition of NULL. I read 4.1.5 as saying "ICE 0 and ICE 0 cast to void* must be legal forms of the null pointer constant", not that they are the only legal forms of the null pointer constant. I didn't write the standard though, so I could be wrong.
janks
I think when the standard says, " such-and-such is called *foo* ", it means to define "foo", not just to give examples of "foo".
Steve Jessop
What I meant to say is that since the moment `NULL` appeared as a specified part of the language, `0` was a valid way to define `NULL`. I.e. there never was a time `NULL` was *requred* to be defined as something non-zero. It could be defined to something platform specific (anything), but `0` was always required to work just as well. (Again: since the moment `NULL` was introduced. CRM C, for example, makes no mention of `NULL`.)
AndreyT
A: 

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.

dan04
A: 

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).

John Saunders
'In the past, this formal meaning of "0" in a pointer context was not established.' -- right, where 'In the past' means 'Before the invention of the C language'.
Windows programmer
@Windows: no, that's not the case. It was not established as of K` did not have a defined meaning.
John Saunders
Windows programmer
+7  A: 

The C FAQ has some examples of historical machines with non-0 NULL representations.

From http://c-faq.com/, 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 * and void *, 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 * and void * pointers than for other pointers.

The Symbolics Lisp Machine, a tagged architecture, does not even have conventional numeric pointers; it uses the pair (basically a nonexistent 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.

janks
+1 for finding the solid examples. FWIW, I think ISO C now mandates that 0 in the source code means null pointer no matter the underlying representation used. But that's from memory and I don't have a copy of C1x floating around at the moment.
paxdiablo
Exactly what I was looking for. Thank you.
Jurily
In C source code the NULL macro would still be either an integer constant expression that evaluates to 0 or a (void *) cast of the same. The resulting pointer value might have a representation that isn't 0 but that doesn't mean the source code can assume it will be 0xffff or anything like that.
Windows programmer
@Windows Programmer: true now that the C standard mandates that NULL is a null pointer constant (as opposed to just any constant equal to a null pointer). All of those machines predate the standard, though, so it's certainly possible that one of them did `#define NULL ((void*)-1)`, or `#define NULL __nullpointer`, where __nullpointer is a compiler built-in, or whatever. They're the right places to be looking for such a thing.
Steve Jessop
@SteveJessop: Can you point any text in the standard that rules out `#define NULL __builtin_compiler_magic_nil_pointer`?
janks
See my other comment. I'm not sure, though, I might take back part of what I said. `__builtin_compiler_magic_nil_pointer` might be all right, provided that the implementation defines (as an extension) that `__builtin_compiler_magic_nil_pointer` is an integral constant expression with value 0, or that it behaves like one in every possible way. g++ in fact does #define NULL to `__null`, but gcc (in C mode) doesn't.
Steve Jessop
Sorry, but the answer is completely bogus. The examples given in C FAQ are the examples of machines with non-zero *null-pointer values*. The question was about non-zero `NULL`, i.e. about a non-zero *null-pointer constant*. Null-pointer value and null-pointer constant are two completely different things. The answer mistakes one for another, which is a rather widespread mistake (a newbie mistake, should I add).
AndreyT
The question was about what `NULL` expands to. This answer has absolutely nothing to do with what `NULL` expands to. Yet, the autor of the question says "Exactly what I was looking for" (???). Apparently the author is confused (misunderstands the difference between NPC and NPV, as I said above).
AndreyT
Windows programmer