70

As the comp.lang.c FAQ says, there are architectures where the null pointer is not all bits zero. So the question is what actually checks the following construction:

void* p = get_some_pointer();
if (!p)
    return;

Am I comparing p with machine dependent null pointer or I'm comparing p with arithmetic zero?

Should I write

void* p = get_some_pointer();
if (NULL == p)
    return;

instead to be ready for such architectures or is it just my paranoia?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • 33
    is this a `C` or `C++` question? in `C++` you should always use `nullptr` – BeyelerStudios Aug 21 '15 at 08:50
  • I want the answer for both languages – ivaigult Aug 21 '15 at 08:51
  • 4
    The actual bit representation of a null pointer is an implementation detail that shouldn't interest you as long as you write standard c. Since if(!p) is defined that should answer your question. The same is true for any other weird implementations - follow the standard and it's the compilers problem to figure out how to make the machine what you want it to. – Voo Aug 21 '15 at 08:54
  • 5
    Incidentally, `NULL` is arithmetic zero, at least in C++ (and I think also in C, modulo a cast). This is true *even if the underlying address is not the physical memory address zero*. That is, in C++ you can treat `NULL` and `0` identically, and they are actually indistinguishable. – Konrad Rudolph Aug 21 '15 at 09:02
  • This is not duplicate. I'm not asking if it possible, I'm asking what to do in such case. – ivaigult Aug 21 '15 at 09:04
  • NULL has always been `0` or `(void*)0` in standard C, it has never been anything else. I suppose it may have been defined as something obscure in pre-C90 code. – Lundin Aug 21 '15 at 09:13
  • Please [edit] clarifications (such as wanting an answer for two languages) into your question. Don't leave such important remarks buried in comments where few will see them. – TRiG Aug 21 '15 at 11:32
  • 4
    One consequence of this is that `memset` to zero may result in non-NULL pointers. Incidentally the type of exotic hardware where this issue may be encountered will likely break a number of other common assumptions about modern architectures. More to the point while I appreciate the desire to write portable code in practice non-trivial C/C++ code _never_ works on these types of far-out platforms unless it has actually been tested on esoteric hardware, at least that has been my experience. – doynax Aug 21 '15 at 12:53
  • @KonradRudolph AFAIK, `NULL` in C++ can also be `nullptr`. – dyp Aug 21 '15 at 15:55
  • Note that in C++, a pointer-to-member to a standard-layout class will have a null pointer value typically represented by **all bits 1**. A pointer-to-member is usually implemented as an offset, and the value 0 hence denotes offset 0, the first member. [Live demo](http://melpon.org/wandbox/permlink/czmyF7wNZtYMKFjZ) – dyp Aug 21 '15 at 16:02
  • Related: http://stackoverflow.com/q/2759845/11683 – GSerg Aug 21 '15 at 20:25
  • 2
    If an answer for two different languages is required there should be two separate questions, the right way of doing this is not necessarily the same in C and C++ (particularly with nullptr in C++) – Vality Aug 21 '15 at 20:27
  • @Vality There is a common C/C++ answer that is right in both languages. – curiousguy Aug 22 '15 at 00:40
  • 1
    @curiousguy These two are different languages and differ in subtle ways. Giving an answer that applies to both is very hard and often impossible when it comes to subtle corner cases of the languages like this one. – fuz Aug 22 '15 at 15:34
  • 1
    @BeyelerStudios: nullptr is a C++ 11 construct. So code that has to be 100% C++ 98 or 03 compatible can't use it. For multiplatform code that has to be able to compile for a bit more exotic platforms this requirement is still quite common nowadays. – Kaiserludi Aug 22 '15 at 22:10
  • Being so used to Java... asking "should I write `if (null == obj)` instead of `if (!obj)`" is just silly. – Ky - Sep 01 '15 at 13:21

5 Answers5

104

According to the C spec:

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. 55) 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.

So 0 is a null pointer constant. And if we convert it to a pointer type we will get a null pointer that might be non-all-bits-zero for some architectures. Next let's see what the spec says about comparing pointers and a null pointer constant:

If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer.

Let's consider (p == 0): first 0 is converted to a null pointer, and then p is compared with a null pointer constant whose actual bit values are architecture-dependent.

Next, see what the spec says about the negation operator:

The result of the logical negation operator ! is 0 if the value of its operand compares unequal to 0, 1 if the value of its operand compares equal to 0. The result has type int. The expression !E is equivalent to (0==E).

This means that (!p) is equivalent to (p == 0) which is, according to the spec, testing p against the machine-defined null pointer constant.

Thus, you may safely write if (!p) even on architectures where the null pointer constant is not all-bits-zero.

As for C++, a null pointer constant is defined as:

A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type.

Which is close to what we have for C, plus the nullptr syntax sugar. The behavior of operator == is defined by:

In addition, pointers to members can be compared, or a pointer to member and a null pointer constant. Pointer to member conversions (4.11) and qualification conversions (4.4) are performed to bring them to a common type. If one operand is a null pointer constant, the common type is the type of the other operand. Otherwise, the common type is a pointer to member type similar (4.4) to the type of one of the operands, with a cv-qualification signature (4.4) that is the union of the cv-qualification signatures of the operand types. [ Note: this implies that any pointer to member can be compared to a null pointer constant. — end note ]

That leads to conversion of 0 to a pointer type (as for C). For the negation operator:

The operand of the logical negation operator ! is contextually converted to bool (Clause 4); its value is true if the converted operand is true and false otherwise. The type of the result is bool.

That means that result of !p depends on how conversion from pointer to bool is performed. The standard says:

A zero value, null pointer value, or null member pointer value is converted to false;

So if (p==NULL) and if (!p) does the same things in C++ too.

Nemo
  • 70,042
  • 10
  • 116
  • 153
ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • 9
    IMHO this is the only *complete* answer so far as if *also* points out that the Standard defined `!E` being the same as `E == 0` (C11 Draft 6.5.3.3/7) etc. – alk Aug 21 '15 at 13:48
  • 1
    Have you misquoted the C++ standard regarding negation operator? It shouldn't have the result of type `int`. As per C++11 5.3.1/9, "the type of the result is `bool`". – Ruslan Aug 21 '15 at 19:11
  • Very detailed answer, great – user1 Aug 22 '15 at 15:16
  • This answer convinced me that `if (! p)` is well-defined. But what about `if (p)`? This may seem ridiculous but the quotes in this answer don’t actually guarantee its semantics. I believe §6.8.4.1p2 does. – Konrad Rudolph Nov 22 '19 at 15:51
32

It doesn't matter if null pointer is all-bits zero or not in the actual machine. Assuming p is a pointer:

if (!p) 

is always a legal way to test if p is a null pointer, and it's always equivalent to:

if (p == NULL)

You may be interested in another C-FAQ article: This is strange. NULL is guaranteed to be 0, but the null pointer is not?


Above is true for both C and C++. Note that in C++(11), it's preferred to use nullptr for null pointer literal.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
12

This answer applies to C.

Don't mix up NULL with null pointers. NULL is just a macro guaranteed to be a null pointer constant. A null pointer constant is guaranteed to be either 0 or (void*)0.

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

66) The macro NULL is defined in <stddef.h> (and other headers) as a null pointer constant; see 7.19.

7.19:

The macros are

NULL

which expands to an implementation-defined null pointer constant;

Implementation-defined in the case of NULL, is either 0 or (void*)0. NULL cannot be anything else.

However, when a null pointer constant is assigned to a pointer, you get a null pointer, which may not have the value zero, even though it compares equal to a null pointer constant. The code if (!p) has nothing to do with the NULL macro, you are comparing a null pointer against the arithmetic value zero.

So in theory, code like int* p = NULL may result in a null pointer p which is different from zero.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • [Related question](http://stackoverflow.com/questions/5142251/redefining-null) that got some nice answers. – Lundin Aug 21 '15 at 09:05
  • 2
    A null pointer constant may be an ICE that evaluates to `0`, e.g. `NULL` may be defined as `(1 - 1)` – M.M Aug 22 '15 at 01:55
  • "Implementation-defined in the case of NULL, is either 0 or void*" - presumably you meant `(void *)0` there – M.M Aug 22 '15 at 01:56
  • 1
    " a null pointer, which may not necessarily be equal to the value zero." - a null pointer is guaranteed to compare equal to `0` – M.M Aug 22 '15 at 01:57
  • 2
    The unary `!` operator is defined such that `!x` is the same as `x == 0`, as `x == 0` has the expected effect with respect to `NULL` pointers, `!x` does check for `NULL` on a platform where the `NULL` pointer is not numerically equal to 0. – fuz Aug 22 '15 at 15:37
  • "However, when a null pointer constant is cast to a pointer, you get a null pointer, which may not have the value zero" - is it because of alignment requirements to pointed type or something else? – Sergio Aug 12 '16 at 09:36
  • @Serhio No, it is because a null pointer is defined as "something that is not pointing at any valid memory". What pointer value that gives such a result is system-dependent. Addess 0 could be valid memory on some systems. It is up to the compiler to solve this in a good way. – Lundin Aug 12 '16 at 11:08
  • The claim that it “cannot be anything else” than `o` or `(void*)o` is wrong, unless you meant to allow a lot of synonyms. For example, a number of implementations on architectures with 16-bit `int` and 32-bit pointers define it as `0L`, and surely the form with a cast would be `((void*)0)` to ensure correct evaluation order. `((uintptr_t)0)` would also be perfectly legal. Some libraries for GCC define `NULL` as the compiler-specific extension `__null`. – Davislor May 06 '17 at 19:22
7

Back in the day, STRATUS computers had null pointers as 1 in all languages.

This caused issues for C, so their C compiler would allow pointer comparison of 0 and 1 to return true

This would allow:

void * ptr=some_func();
if (!ptr)
{
    return;
}

To return on a null ptr even though you could see that ptr had a value of 1 in the debugger

if ((void *)0 == (void *)1)
{
    printf("Welcome to STRATUS\n");
}

Would in fact print "Welcome to STRATUS"

Glenn Teitelbaum
  • 10,108
  • 3
  • 36
  • 80
  • 2
    That the null pointer constant can have any representation is not historical and it is not a single-architecture extension. It's a core fact of both languages. Abstraction is the whole purpose of those languages, after all. – Lightness Races in Orbit Aug 22 '15 at 11:51
  • @LightnessRacesinOrbit I'm not convinced of that. The C standard was effectively forced to support null pointers with set bits because there were already implementations out there that had such null pointers. If all implementations had made all-bits-zero a null pointer, then I'm quite convinced that the standard would have required that. It was later required for integer types because it was how all implementations worked anyway, and it turned out to be useful for programs (think `calloc` and such). How would that exact same argument not apply for pointer types? –  Aug 22 '15 at 13:44
  • @hvd: I'm not convinced of it. What business is it of the standard to mandate that? The standard writers know not only (as you say) that this was the case in the past, but more importantly that it can become the case at any point in the future, and that there is zero reason to constrain it in a language abstraction. – Lightness Races in Orbit Aug 23 '15 at 10:26
  • @LightnessRacesinOrbit Well, what do you see as the difference between integer types and pointer types, then? What makes it okay to require all bits zero to represent zero, but not okay to require all bits zero to represent a null pointer? The only significant difference I can think of is that there have been actual real-world implementations in the past where all bits zero did not represent a null pointer. For integer types, there are also legitimate reasons for making all bits zero represent something other than zero, so why do your arguments not apply there? –  Aug 23 '15 at 17:33
  • @hvd: The representation of integers isn't standard-mandated either. The only requirement is that the unsigned ones have modulo arithmetic. – Lightness Races in Orbit Aug 23 '15 at 18:40
  • @LightnessRacesinOrbit It used to not be, but is now: http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_263.htm (That's for C. C++ doesn't explicitly require it, but does require binary compatibility with a conforming C implementation.) –  Aug 23 '15 at 19:01
  • @hvd: And that DR was actually accepted into a standard? (Even if so, it would have to be C11 ... and even C++14 is "still" based on C99.) – Lightness Races in Orbit Aug 23 '15 at 22:19
  • @LightnessRacesinOrbit It made it into C99 TC2, so it already applies to C++ as well. –  Aug 23 '15 at 22:31
  • @hvd: Okay​​​​​. Kinda sad :( – Lightness Races in Orbit Aug 23 '15 at 22:43
  • 1
    For the curious, I found a document that describes the null pointers of the Stratus computers: http://ftp.stratus.com/vos/doc/reference/page_0_control.txt – Tor Klingberg Sep 04 '15 at 11:55
2

If your compiler is any good there are two things (and only two things) to watch out for.

1: Static default initialized (that is, not assigned) pointers won't have NULL in them.

2: memset() on a struct or array or by extension calloc() won't set pointers to NULL.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 3
    You are correct about (2) but wrong about (1). C++03 section 3.6.2 says "Objects with static storage duration shall be zero-initialized (8.5)", and paragraph 8.5 (5) defines "zero-initialize" for a scalar type T as converting the constant 0 to type T. So static duration pointers are initialized to NULL even when NULL is not all-zero. (The C specs contain similar wording.) – Nemo Aug 22 '15 at 01:05
  • The C++ standard says that pointers of static storage duration without an initializer must be initialized to `nullptr` . If your compiler doesn't do this then it is not any good ... – M.M Aug 22 '15 at 01:52
  • 1
    @MattMcNabb: I think he means "if your compiler is buggy there may be other cases". – Harry Johnston Aug 22 '15 at 05:39
  • @Matt McNabb: That wasn't always true and compilers for the remaining architectures like these tend to implement old versions of the standard. – Joshua Aug 22 '15 at 15:02
  • @Joshua all C and C++ standards have said that – M.M Aug 23 '15 at 01:52
  • 1
    @Joshua: As Matt says, every version of every C and C++ standard has guaranteed that static duration pointers are correctly initialized to NULL, just as if you had written `... = 0;`. You are simply wrong about (1) with regard to every standard-conforming compiler ever. – Nemo Aug 24 '15 at 12:10
  • 1
    I could swear my book said it was binary 0, and it was true of the only one I ever used on a platform that didn't have NULL = binary zero. – Joshua Aug 24 '15 at 15:13
  • @Joshua: Original K&R maybe? I don't have my copy handy... But this has been true at least since C89 (see http://port70.net/~nsz/c/c89/c89-draft.html#3.5.7 ; look for "null pointer") – Nemo Aug 24 '15 at 20:53