33

In some legacy code I came across the following null pointer check.

if( myPtr > 0 ) {
...
}

Are there any technical risks of checking for a null pointer via this if-check?

phuclv
  • 37,963
  • 15
  • 156
  • 475
ucczs
  • 609
  • 5
  • 15
  • 3
    IIRC this is technically invalid: you can only compare pointers that point to the same array/object. Comparing unrelated pointers for order is invalid. – Konrad Rudolph Mar 28 '22 at 10:56
  • 1
    @KonradRudolph iirc comparing to null is an exception. – 463035818_is_not_an_ai Mar 28 '22 at 10:56
  • 4
    @463035818_is_not_a_number No, it isn’t. Equality comparison is valid between any two pointers. Order comparison isn’t. – Konrad Rudolph Mar 28 '22 at 11:04
  • 1
    @KonradRudolph Even if you use `std::greater`? – Ted Lyngmo Mar 28 '22 at 11:13
  • 1
    I wonder if that code snippet is just a typo. – Paul Sanders Mar 28 '22 at 11:17
  • 10
    Conceivably, a pointer could be *smaller* than 0. ;-) Even if this compiled. The only guarantee for an object is that its address is *not equal* to nullptr. – Peter - Reinstate Monica Mar 28 '22 at 11:18
  • 1
    @KonradRudolph equality comparison is not valid between a pointer-to-object and a pointer-to-one-past-end https://eel.is/c++draft/expr.eq#3 *If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object,72 the result of the comparison is unspecified.* https://godbolt.org/z/4aqGMWGfq – Aykhan Hagverdili Mar 28 '22 at 11:19
  • 1
    @Peter-ReinstateMonica I was thinking the same thing. There's no guarantee that [this example](https://godbolt.org/z/GxM8jaMqE) will print _"greater"_. It could just as well have printed _"less"_, – Ted Lyngmo Mar 28 '22 at 11:20
  • 1
    @TedLyngmo Why would `std::greater` be any different? – Konrad Rudolph Mar 28 '22 at 11:35
  • 11
    @KonradRudolph *`std::greater` is different* https://en.cppreference.com/w/cpp/utility/functional/greater A specialization of std::greater for any pointer type yields the implementation-defined strict total order, even if the built-in > operator does not. – Aykhan Hagverdili Mar 28 '22 at 11:37
  • @AyxanHaqverdili “unspecified” ≠ “invalid”! – Konrad Rudolph Mar 28 '22 at 11:38
  • @AyxanHaqverdili Ah, good to know. – Konrad Rudolph Mar 28 '22 at 11:38
  • @KonradRudolph for all intents and purposes, you shouldn't rely on unspecified behavior :) – Aykhan Hagverdili Mar 28 '22 at 11:39
  • @AyxanHaqverdili Re "equality comparison is not valid between a pointer-to-object and a pointer-to-one-past-end": it is entirely valid (and performed all the time when iterating arrays) for pointer to object and pointer one-past *the same object*. The quoted passage mentions "the address one past the last element of a **different** complete object". (Conceivably, that one-past-the-end pointer could point to an adjacent object and hence compare equal.) – Peter - Reinstate Monica Mar 28 '22 at 11:56
  • @Peter-ReinstateMonica my point stands that there exists at least one case where equality comparing two well-defined pointers yields unspecified result. – Aykhan Hagverdili Mar 28 '22 at 12:35
  • related: [Is a pointer considered an unsigned value in C++?](https://stackoverflow.com/q/48429021/995714), [Pointer comparisons in C. Are they signed or unsigned?](https://stackoverflow.com/q/6702161/995714). Pointers can be compared as signed or unsigned internally, that's one of the reasons why it's UB – phuclv Mar 29 '22 at 08:23
  • @Peter-ReinstateMonica, you mentioned that "a pointer could be smaller than 0". Can you give an **explicit example** how a pointer can smaller than 0? – ucczs Mar 29 '22 at 11:38
  • They can compare unequal, while ordered comparison using >, >=, <, <= invokes undefined behaviour. – gnasher729 Mar 29 '22 at 13:06
  • Possibly duplicate of [Given that p is a pointer is "p > nullptr" well-formed?](https://stackoverflow.com/q/26590267/1708801) – Shafik Yaghmour Mar 29 '22 at 20:35
  • @Peter-ReinstateMonica: In the language actually processed by clang and gcc, an equality comparison between a just-past pointer and a pointer to the next object may not only yield true or false in arbitrary fashion, but may also cause surrounding code to behave nonsensically as well. – supercat Mar 29 '22 at 22:08
  • Very similar code to this caused problems when people started exceeding 2GB of address space in 32-bit code. If you did this kind of thing on pointers using signed types for the comparisons, pointers to the second half of the address space might compare less than zero. It's wrong for the same reason checking if a `char` is less than 0 is wrong because you're just hoping that `char` is signed. – David Schwartz Mar 29 '22 at 22:12
  • @DavidSchwartz: It's interesting to note that on 16-bit x86, even though ptrdiff_t was generally signed 16 bits, objects over 32,768 bytes were common. For any `char *p,*q;` that identified parts of the same object, the value of `q-p` would be such that `p+(q-p)` would equal `q`, since computing `(q-p)` would result in numerical wraparound, but adding that value to `p` would cause a wraparound in the opposite direction. The Standard didn't consider such possibilities, but treating things that way was much more practical than a 32-bit `ptrdiff_t` would have been. – supercat Mar 29 '22 at 22:27

3 Answers3

39

Ordered comparison between a pointer and an integer is ill-formed in C++ (even when the integer is a null pointer constant such as it is in this case). The risk is that compilers are allowed to, and do, refuse to compile such code.


You can rewrite it as either of these:

if(myPtr != nullptr)
if(myPtr)
eerorika
  • 232,697
  • 12
  • 197
  • 326
19

0 is a NULL constant. So this is equivalent to ptr > (void*)0.

Problem is that > on pointers in C++ is only valid for pointers into the same object or array. This rule originated from back when relatively insane stuff like segmented memory was more common, but it permits optimizations and assumptions by the compiler.

For example, suppose px is a pointer into an array, and it currently points at the first element. Then px>py is true unless py is also a pointer to the first element. Or, if px is a pointer to one-past-the-end of an array, then px<py is always false.

Using that kind of knowledge, compilers can understand refactor and rewrite loop bounds, which in turn can let the compiler produce much faster code.

But this also means that px>py could be true in one spot and not in another, because the compiler can prove a fact about py in one spot but not another.

In short, be very careful about pointer comparisons.

Meanwhile, equality between null and any other pointer is well defined.

Also std::less is guaranteed to be well behaved, and agree with < when < is defined; this permits maps of pointers to work.

In this case, the proper rewrite is:

if(myPtr!=nullptr)

which will fail to compile if myPtr isn't a pointer, or

if(myPtr)

which has the risk that if myPtr is a signed number type you just changed behaviour. I'd do the first.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 4
    _This rule originated from back when relatively insane stuff like segmented memory was more common_ => Of note, modern architectures like CHERI are "effectively" segments, and comparing pointers of various provenance may end up triggering faults. – Matthieu M. Mar 29 '22 at 11:05
1

As others said, the comparison is invalid so the behavior depends on compiler. It's possible that many OSes consider pointers as signed because they split the address space into halves with the negative one belongs to the kernel so compilers there may emit signed comparisons for myPtr > 0

There are a couple of cases I've seen compilers consider pointers as signed but I couldn't find them right now. But one notable example is the POINTER_SIGNED and POINTER_UNSIGNED macros in Windows which resolves to __sptr and __uptr

See also

phuclv
  • 37,963
  • 15
  • 156
  • 475