Why is C++'s NULL typically an integer literal rather than a pointer like in C?
Asked Answered
M

5

41

I've been writing C++ for many years, using nullptr for null pointers. I also know C, whence NULL originates, and remember that it's the constant for a null pointer, with type void *.

For reasons, I've had to use NULL in my C++ code for something. Well, imagine my surprise when during some template argument deduction the compiler tells me that my NULL is really a ... long. So, I double-checked:

#include <type_traits>
#include <cstddef>

static_assert(not std::is_same<decltype(NULL), long>::value, "NULL is long ???");

And indeed, the static assertion fails (with GCC and with Clang).

I checked on cppreference.com, and sure enough (C++11 wording):

The macro NULL is an implementation-defined null pointer constant, which may be an integer literal with value zero, or a prvalue of type std::nullptr_t.

Why does this make sense? In itself, and in light of the incompatibility of C?

Merete answered 4/9, 2021 at 16:37 Comment(3)
Comments are not for extended discussion; this conversation has been moved to chat.Photomicroscope
einpoklum, "I also know C, whence NULL originates, and remember that it's the constant for a null pointer, with type void *." is too narrow as other possibilities exists in C. What is your C reference to support your assertion that it is certainly a void *?Alvy
@chux-ReinstateMonica: You are actually quite right. My assumption about NULL in C is just folklore, that is part of the truth.Merete
C
49

In C, a void* can be implicitly converted to any T*. As such, making NULL a void* is entirely appropriate.

But that's profoundly dangerous. So C++ did away with such conversions, requiring you to do most pointer casts manually. But that would create source-incompatibility with C; a valid C program that used NULL the way C wanted would fail to compile in C++. It would also require a bunch of redundancy: T *pt = (T*)(NULL);, which would be irritating and pointless.

So C++ redefined the NULL macro to be the integer literal 0. In C, the literal 0 is also implicitly convertible to any pointer type and generates a null pointer value, behavior which C++ kept.

Now of course, using the literal 0 (or more accurately, an integer constant expression whose value is 0) for a null pointer constant was... not the best idea. Particularly in a language that allows overloading. So C++11 punted on using NULL entirely over a keyword that specifically means "null pointer constant" and nothing else.

Cud answered 4/9, 2021 at 16:44 Comment(12)
Is "the literal 0 acts as a null pointer constant" a C++ innovation? Stuff like the C FAQ cites K&R to say that this happened back in C as well.Solubilize
@NathanPierson: That may be the case; I don't know C's history that well.Cud
@NathanPierson: C has definitely had that feature for a long time (e.g. it's present in my paper copy of K&R 2nd edition ANSI C, the oldest C book I have), but it's possible that very early C compilers needed NULL defined as ((void*)0). Unless the NULL macro predates the introduction of void* in C, which might well be the case; bell-labs.com/usr/dmr/www/chist.html says K&R first edition had no mention of the void type, which the language had by 1982. But still NULL probably predates that; stdio.h is very old and does use that for error returns.Malefaction
Wait. Suppose I buy the argument about no implicit conversion + source compatibility. So, why not use uintptr_t then? Or whichever fundamental integer type has the same size as a pointer?Merete
@Merete not on all platforms a pointer is the size of a integral type. sometimes a pointer is even bigger than all integral types or even smaller.Scotfree
@Raildex: Interesting... could you possibly link to an example?Merete
@einpoklum: "So, why not use uintptr_t then?" ... what does it matter? It's a null pointer constant; it's job is to initialize a pointer. Its type, beyond being an integral type, is generally irrelevant. What matters is that it is a constant expression whose value is zero.Cud
In C, a null pointer constant is an integer constant expression that evaluates to zero. C++[11] imposed additional restaints.Hemidemisemiquaver
@PeterCordes In "early" K&R C, you didn't need any cast to convert from integers to pointers and back (and void wasn't a keyword at all), so it would've been pointless to cast the 0. Things like #define FOO 0170200 followed by struct foo { int bar; }; ...; FOO->bar = 17 were perfectly valid and used all over the place. FWIW, the Unix version 7 sources had #define NULL 0.Venous
in C any integer constant expression evaluating to 0 is a null pointer constant; it would be conforming for the implementation headers to do #define NULL ( 3 - 3 )Bouillon
@Peter, K&R first edition didn't have "void" and defines NULL as 0. (In example code, page 97).Qua
@PeterCordes Before ANSI C in 1989, C compilers varied considerably. I had one that definitely did not conform to the standard as it came out, although I don't remember how it defined NULL.Rauwolfia
W
12

In both languages, NULL is an implementation-defined null pointer constant. C++ section 17.2.3, which refers to C 7.19 for the definition.

That means that in C, NULL can be defined as an integer type or as a void* type, and in C++ can be defined as an integer type; it could also be defined as a nullptr_t if I've interpreted the standard correctly. A hidden (non-normative) footnote says that (void*)0 can't be a valid definition, and that makes sense because C++ doesn't have C's implicit conversion from void* to other pointer types.

If you have a C compiler that defines it as ((void*)0) and a C++ compiler that defines it as 0L, they are both conformant. Both implementations satisfy the use cases (can assign from and compare with NULL).

C++ introduced nullptr primarily to give a typed value suitable for overloading, as it was considered surprising that passing NULL to a function could select an integer version.

Waldman answered 5/9, 2021 at 12:12 Comment(23)
1. This doesn't answer the question. 2. In C++, it is not a "null pointer constant", it is either that or a plain integral value. And 0L is not a "null pointer constant" - but it's what GCC and clang use.Merete
1. It refutes the premise of the question - both specifications say it's implementation-defined, and it just has to be a null-pointer constant. 2. The C++ standard says exactly "The macro NULL is an implementation-defined null pointer constant. See also: ISO C 7.19" I don't see how you can be more authoritative than that.Waldman
1. The question itself states it can be either a literal or a null pointer constant; but I've rephrased the title to clarify this point. 2. The footnote in the standard defines "null pointer constant" as not a pointer, i.e. it's a misleading definition.Merete
Ah, so you're now asking about implementation choices. That's different. In that case, the real reason is that that's what's easiest for the compiler/library authors. Likely to two particular cases: in C, it's handy to be able to pass NULL to a varargs function expecting a pointer, and in C++, not having to special case null pointer literals from the void*T* conversion rules (and library definition written before nullptr available).Waldman
@Merete Both 0 and 0L are null pointer constants (no quotes) in C++, because the standard says so.Fernand
In C++ (void*)0 is not a null pointer constant because.Fernand
@n.1.8e9-where's-my-sharem.: In that case, the phrase is meaningless. The standard could say these are "pink unicorns" and NULL must be defined to be a pink unicorn. But if it has a horn it can't be a pink unicorn! The standard says so etc.Merete
@Merete The C++ standard says that 0 is one of several forms of a null pointer constant. The C standard says the exact same thing. The C++ standard also says that (void*)0 is not a form of a null pointer constant. The C standard says the opposite thing. What exactly doesn't make sense to you here, and why?Fernand
@TobySpeight: In C++, (void*)0 would not be a valid definition of NULL. It can't be implicitly converted to pointers of other types. (godbolt.org/z/sPdns9YGb). Your 2nd paragraph says it would be valid in C and C++, but it's actually only valid in C. I considered editing myself to fix that, but other parts of your answer seem to be phrased on the idea that either language could have made either choice of definition.Malefaction
@n.1.8e9-where's-my-sharem.: Again, if the standard doesn't say 0L is a pointer, then it's just poor phrasing to say that it is a "null pointer constant".Merete
@Merete "Null pointer constant" is a term for something that is both (a) a constant and (b) denotes a null pointer in contexts where a pointer is needed. 0L is a constant of type long, which is definitely not a pointer. This doesn't prevent it from being a null pointer constant. If you don't like the term, you can try and come up with a better one, perhaps enough people will like it. If you don't like the fact that 0L is able to stand for a null pointer, tough luck, because backwards compatibility.Fernand
@n.1.8e9-where's-my-sharem.: "Pink unicorn" is a term for something that is both (a) pink and (b) denotes a unicorn where a unicorn is needed. Oh, yeah, and, one more thing: It's not a unicorn.Merete
@einpoklum: "it's just poor phrasing to say that it is a "null pointer constant"" I don't see how it matters if it is "poor phrasing". The standard has a clear definition for the term and how it gets used. You may not like the term, but it is what it is.Cud
@Peter I think a compiler is entitled to consider (void*)0 to be a null pointer constant: arguably, the cast doesn't change it into a non-null pointer or to non-constant (please do explain if I have a misunderstanding there). A standard library implementation for a compiler that carries that nullness could use ((void*)0) as NULL, I think.Waldman
@Merete this answer makes a factually incorrect statement. I said so above in the comments. You also make a factually incorrect statement, here: "in C, NULL is a pointer" (it is an implementation-defined null pointer constant, not necessarily a pointer).Fernand
@TobySpeight: I'm not talking about whether (void*)0 can be called a "null pointer constant" in C++. Regardless of that, it certainly can't be how NULL is defined, because a C++ compiler must refuse to allow implicit conversions like int *p = (void*)0. eel.is/c++draft/support.types.nullptr#2 has a footnote that explicitly says (void*)0 is not a valid definition for NULL.Malefaction
@Peter, what version are you reading that has footnotes? The one you link to doesn't.Waldman
On my Chromium, that link renders to include eel.is/c++draft/support.types.nullptr#footnote-176 "176) Possible definitions include 0 and 0L, but not (void*)0." With a superscript 176 earlier referring to it.Malefaction
re: whether (void*)0 is a "null pointer constant", eel.is/c++draft/conv.ptr#1 says A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type. Since (void*)0 doesn't meet that requirement, it's not a null pointer constant in C++. So the exclusion of (void*0) as a definition for NULL is because it's not a null pointer constant, not because of extra requirements for a valid NULL definition.Malefaction
Ah, that's strange - here in Firefox, that content exists, but is styled with display:none; due to normative-only.css. I don't know how you got it visible. [Later - I see there's a script attached; perhaps running the script unhides it? That's evil]Waldman
@PeterCordes regarding your most recent comment - the text you quote is defining the behaviour of null pointer constants, not providing a definition for which expressions are null pointer constants . The previous sentence to what you quoted provides the definition: "an integer literal with value zero or a prvalue of type std::nullptr_t".Bouillon
@TobySpeight: Firefox 90.0 on Arch GNU/Linux shows the footnote, too, and I have no extensions installed in that browser. I'm using uMatrix in chromium, but there are no 3rd-party .js links, or even any separate .js files, so I'm not blocking anything in Chromium either. Maybe you are? And yeah that seems weird, it's not like you have to click to unhide them.Malefaction
@M.M: I chose to quote the 2nd sentence instead of trying to prove that the first was an exhaustive definition of the only things a null pointer constant could be, rather than just examples. I think it's fairly clear it's phrased as an exhaustive definition, but the 2nd sentence was even more rock solid for the point I wanted to make about (void*)0. But yes, thanks for also quoting the actual definition.Malefaction
R
10

C does not simply define NULL as (void *)0. It's also possible to define it as 0 or any constant expression that evaluates to it. Using #define NULL 0 works in either language.

Edit: this answer is very good and more detailed.

Rauwolfia answered 7/9, 2021 at 1:54 Comment(1)
You're quite right. Suggest you also link to this SO answer about the type of NULL.Merete
I
6

My assumption has always been that implementations' decisions to keep NULL as 0 was a matter of backwards compatibility with C++98. I've seen a lot of code using NULL in non-pointer contexts, particularly as a (probably unintentional, or at least uninformed) substitute for a null terminator in an array. Since the best practice since '11 has been "don't use NULL at all", redefining NULL to nullptr would potentially break bad/old code, while doing very little to help good/new code.

Idiot answered 5/9, 2021 at 20:45 Comment(5)
I was asking about how NULL was set in the first place. Naturally they wouldn't change it once it got a value...Merete
Er... in C++98, what else could it have been? They didn't have the foresight to make nullptr, and they couldn't use (void*)0, so they really had just the one option. (With an optional L for good measure.)Idiot
1. A value was chosen well before C++98 was standardized 2. You're answering a different question than the one I asked.Merete
@einpoklum: "A value was chosen well before C++98 was standardized" A value that wouldn't work for C++. I think an answer mentioned that.Cud
@einpoklum: Before C++98 was standardized? You mean in early pre-standard C++, after disallowing implicit conversion from void*? Some of those early C++ implementations worked by translating to C, so it would certainly be sensible to pick a definition that didn't require additional support, such as 0 or 0L, from the PoV of doing something that worked right away so they could get one with implementing / designing the rest of the language.Malefaction
L
0

In

vcruntime.h

define as below:

#ifdef __cplusplus
    #define NULL 0
#else
    #define NULL ((void *)0)
Lunde answered 9/9, 2021 at 5:35 Comment(2)
An interesting data point... but it doesn't answer the "why".Merete
@Merete I agree with TobySpeight "because C++ doesn't have C's implicit conversion from void* to other pointer types." So it will result in an error when you write int* p = (void*)0; but it works with void* p = (void*)0;Lunde

© 2022 - 2025 — McMap. All rights reserved.