Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the expression "(ptr == 0) != (ptr == (void*)0)" really be true?

Tags:

c++

pointers

I read this claim in a forum thread linked to in a comment by @jsantander:

Keep in mind that when you assign or compare a pointer to zero, there is some special magic that occurs behind the scenes to use the correct pattern for the given pointer (which may not actually be zero). This is one of the reasons why things like #define NULL (void*)0 are evil – if you compare a char* to NULL that magic has been explicitly (and probably unknowingly) turned off, and an invalid result may happen. Just to be extra clear:

(my_char_ptr == 0) != (my_char_ptr == (void*)0) 

So the way I understand it, for an architecture where the NULL pointer is, say, 0xffff, the code if (ptr), would compare ptr to 0xffff instead of to 0.

Is this really true? Is it described by the C++ standard?

If true, it would mean that 0 can be safely used even for architectures that have a non-zero NULL pointer value.

Edit

As an extra clarification, consider this code:

char *ptr; memset(&ptr, 0, sizeof(ptr)); if ((ptr == (void*)0) && (ptr != 0)) {     printf("It can happen.\n"); } 

This is how I understand the claim of this forum post.

like image 366
sashoalm Avatar asked Apr 08 '14 12:04

sashoalm


2 Answers

There's two parts to your question. I'll start with:

If true, it would mean that 0 can be safely used even for architectures that have a non-zero NULL pointer value.

You are mixing up "value" and "representation". The value of a null pointer is called the null pointer value. The representation is the bits in memory that are used to store this value. The representation of a null pointer could be anything, there is no requirement that it is all-bits-zero.

In the code:

char *p = 0; 

p is guaranteed to be a null pointer. It might not have all-bits-zero.

This is no more "magic" than the code:

float f = 5; 

f does not have the same representation (bit-pattern in memory) as the int 5 does, yet there is no problem.

The C++ standard defines this. The text changed somewhat in C++11 with the addition of nullptr; however in all versions of C and C++, the integer literal 0 when converted to a pointer type generates a null pointer.

From C++11:

A null pointer constant is an integral constant expression 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. Such a conversion is called a null pointer conversion.

0 is a null pointer constant, and (char *)0 for example is a null pointer value of type char *.

It's immaterial whether a null pointer has all-bits-zero or not. What matters is that a null pointer is guaranteed to be generated when you convert an integral constexpr of value 0 to a pointer type.

Moving onto the other part of your question. The text you quoted is complete garbage through and through. There's no "magic" in the idea that a conversion between types results in a different representation, as I discuss above.

The code my_char_ptr == NULL is guaranteed to test whether or not my_char_ptr is a null pointer.

It would be evil if you write in your own source code, #define NULL (void*)0. This is because it is undefined behaviour to define any macro that might be defined by a standard header.

However, the standard headers can write whatever they like so as the Standard requirements for null pointers are fulfilled. Compilers can "do magic" in the standard header code; for example there doesn't have to be a file called iostream on the filesystem; the compiler can see #include <iostream> and then have hardcoded all of the information that the Standard requires iostream to publish. But for obvious practical reasons, compilers generally don't do this; they allow the possibility for independent teams to develop the standard library.

Anyway, if a C++ compiler includes #define NULL (void *)0 in its own header, and as a result something non-conforming happens, then the compiler would be non-conforming obviously. And if nothing non-conforming happens then there is no problem.

I don't know who the text you quote would direct its "is evil" comment at. If it is directed at compiler vendors telling them not to be "evil" and put out non-conforming compilers, I guess we can't argue with that.

like image 192
M.M Avatar answered Sep 18 '22 06:09

M.M


I think the forum post you link to is incorrect (or we have misinterpreted what it means by !=). The two sub-expressions have different semantics but the same result. Assuming that my_char_ptr really has type char* or similar, and a valid value:

my_char_ptr == 0 converts 0 to the type of my_char_ptr. That yields a null pointer because 0 is an example of a so-called "null pointer constant", which is defined in the standard. It then compares the two. The comparison is true if and only if my_char_ptr is a null pointer, because only null pointers compare equal to other null pointers.

my_char_ptr == (void*)0 converts my_char_ptr to void*, and then compares that to the result of converting 0 to void* (which is a null pointer). The comparison is true if and only if my_char_ptr is a null pointer because when you convert a pointer to void* the result is a null pointer if and only if the source is a null pointer.

The issue of whether null pointers are represented with 0 bits or not is interesting but irrelevant to the analysis of the code.

The practical danger of thinking that NULL is a null pointer (rather than merely a null pointer constant) is that you might think that printf("%p", NULL) has defined behaviour, or that foo(NULL) will call the void* overload of foo rather than the int overload, and so on.

like image 32
Steve Jessop Avatar answered Sep 22 '22 06:09

Steve Jessop