1

I know that NULL is always 0, but why does the following code print the message?

#include <iostream>

using namespace std;

int main() {
    int* ptr = nullptr;
    if (ptr == 0) {
        cout << "Does it always work?";
    }
    return 0;
}
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
catfour
  • 119
  • 3
  • 10
  • 1
    *"why does the following code print the message"* Why you don't expect it to? – HolyBlackCat Apr 13 '20 at 22:47
  • "_I know that `NULL` is always `0`_" is incorrect. On my platform, `NULL` is #define'd to be `__null`. – Eljay Apr 13 '20 at 22:49
  • At the assembly level there is no difference. The difference is C++ type semantics. – Michael Chourdakis Apr 13 '20 at 22:49
  • @Eljay, according to the answer [here](https://stackoverflow.com/a/49145382/6784796) "because NULL is 0", and I thought that NULL is defined as `#define NULL 0` – catfour Apr 13 '20 at 22:55
  • @catfour The phrase "because NULL is 0" doesn't mean NULL must be defined as `#define NULL 0`. It's a bit simplified anyway. – Asteroids With Wings Apr 13 '20 at 22:56
  • @AsteroidsWithWings, But I see many people initialize the pointers with `0`'s instead of `NULL` and it works well (before `nullptr`). Could you please give more details about your point of view? – catfour Apr 13 '20 at 23:01
  • @catfour It's not my point of view, but a basic fact about the language. C++ provides _conversions_. (e.g. `1` can become `true`). That does not mean that the macro `NULL` is defined as `#define NULL 0`. – Asteroids With Wings Apr 13 '20 at 23:03

1 Answers1

4

Yes.

A pointer initialised from nullptr is a null pointer.

Comparing a null pointer to the literal 0 (or to a std::nullptr_t, which nullptr is; together these are null pointer constants) yields true, always.

You can't do this with any old expression, though; even if integer i is 1, i-i is not a valid null pointer constant, despite evaluating to 0 at runtime. Your program will not compile if you try to do this. Only the literal 0 is a valid null pointer constant that can be compared to pointers.

Also, that does not necessarily mean that it has all bits zero, though! Much like how converting a bool to int gives you zero or one, but the actual underlying bits of the bool can be whatever the implementation likes.

Finally, note that your terminology is slightly off; per [support.types.nullptr/1], nullptr itself has no address that can be taken

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
  • 1
    So, it doesn't matter if I initialized an array of pointers with zeros `memset(arr, 0, sizeof arr)` instead of `arr[i]=nullptr` in a `for` loop, right? – catfour Apr 13 '20 at 23:14
  • 1
    @catfour No `memset` is not safe. This answer already says that: "*that does not necessarily mean that it has all bits zero, though!*". You should avoid using `memset` at all in C++. Use `std::fill` instead, if you don't want to write loops. – walnut Apr 14 '20 at 01:24
  • 1
    @walnut OK thank you, but I disagree with "You should avoid using `memset` at all in C++. Use `std::fill` instead" because `memset` is faster than `std::fill` and we can initialize the array with `0` or `-1` safely using `memset` (it's very useful in competitive programming). – catfour Apr 14 '20 at 01:40
  • 1
    @catfour Compilers dispatch `std::fill` calls to `memset` calls if that is possible (i.e. for trivially-copyable types): https://godbolt.org/z/q8-qMF The benefit of `std::fill` is that it works for *every* type, not only trivially-copyable ones, and is type-safe. Performance is generally not a good reason to use `memset`. – walnut Apr 14 '20 at 01:45
  • @catfour `memset` is not faster than `std::fill`. `std::fill` will invoke `memset` if it can. It's a zero-cost wrapper that gives you type safety and more features where needed. This "competitive programming" just seems to be a race to make the worst code the fastest. Well done, I guess...‍♂️ – Asteroids With Wings Apr 14 '20 at 12:53
  • @catfour In fact, this is a good example, because with your `memset` you are just zeroing bytes. If "null pointer" is not represented that way on your platform, your program is broken. `std::fill`, being typesafe, will do it properly (with any necessary conversions). Admittedly in that instance you probably don't get the `memset` "optimisation" but that's because it wouldn't work (as previously mentioned). – Asteroids With Wings Apr 14 '20 at 12:55
  • @AsteroidsWithWings OK, thank you for this information. Isn't `memset` faster with the optimization level `-O2`? – catfour Apr 14 '20 at 13:20
  • @catfour: It's possible that with optimisations _turned off_, function call overhead in the wrapper comes into play. But comparing performance in the "no optimisation" mode is a fool's errand (no offence). With `-O2`, I can't imagine _any_ difference. – Asteroids With Wings Apr 14 '20 at 14:02
  • @AsteroidsWithWings I will not use useless wrappers to the function I want to call just for the sake of it. – ScienceDiscoverer Aug 02 '23 at 15:39