27

This question is based on code that I found that monitors possible memory leaks, so it contains some code that you probably don't want to see in regular programs like ordering pointers.

However, I saw that a pointer was set to nullptr and then the pointer was compared to a maximum address. Is it guaranteed by the C++ standard that nullptr is always smaller than other pointers for operator<?

stefaanv
  • 14,072
  • 2
  • 31
  • 53
  • "smaller than other pointers" is different from "compared to a maximum address." Are you curious if their comparison is safe? Or if comparing `nullptr` in general is safe? – scohe001 Dec 07 '20 at 16:42
  • 2
    Related: [Using std::less with nullptr](https://stackoverflow.com/q/28811034/2602718) – scohe001 Dec 07 '20 at 16:43
  • 6
    Pointer comparison is only well defined for elements of the same array, or with `==` and `!=`. Comparing pointers to objects that are not in the same array with `operator<` is unspecified. – François Andrieux Dec 07 '20 at 16:44
  • You can only compare less than and greater than for pointers if they point into the same array. – NathanOliver Dec 07 '20 at 16:44
  • 2
    FYI: [std::less](https://en.cppreference.com/w/cpp/utility/functional/less): _A specialization of std::less for any pointer type yields a strict total order, even if the built-in operator< does not._ Though, I saw no statement about how `nullptr` compares to any other pointer. – Scheff's Cat Dec 07 '20 at 16:45

3 Answers3

26

Can you compare nullptr to other pointers for order?

No, you cannot have ordered comparisons of nullptr or other null pointer constants with pointers.


For the rest of my answer, I cover "Can you compare pointer with a null value to other pointers for order?"

Yes. But whether the result is useful is another matter.

Is it always smaller?

No. Unless the other operand is also null, neither operand is guaranteed to compare greater or smaller in this case.

Standard quote (latest draft):

[expr.rel]

The result of comparing unequal pointers to objects is defined in terms of a partial order consistent with the following rules:

  • [does not apply] If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript is required to compare greater.
  • [does not apply] If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member is required to compare greater provided the two members have the same access control ([class.access]), neither member is a subobject of zero size, and their class is not a union.
  • [applies] Otherwise, neither pointer is required to compare greater than the other.

You should use std::less to compare pointers if you need a strict total order. Null is still not guaranteed to compare as smallest value.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 2
    I don't get the last link. Even on systems that have "pointers are integers, nullptr is just 0", the question whether `nullptr` is smallest still depends on the question whether the system treats pointers as signed or unsigned integers. That's pretty arbitrary; e.g. I don't think x86 has a natural choice. (You can use either `ja` or `jg` on pointers). – MSalters Dec 08 '20 at 01:01
  • @MSalters the article is about both non zero null and pointers with different representation depending on type. Certain modes of x86 were an example of latter which isn't quite relevant to this topic. The article has other examples that are about non zero null which I assume affects the ordering. – eerorika Dec 08 '20 at 08:48
  • 1
    I think you missed the point of my comment. Even on x86, a compiler that uses all-bits-zero as `nullptr` and compares pointers with `jg` (jump greater, signed) will have many pointers that are smaller than `nullptr`. This makes perfect sense as a design, e.g. on classic Win32 those are pointers to the system address space. Pointers greater than `nullptr` would point to the user address space. That's why Win32 executables today have a "Large Address Aware" flag - it tells the OS that the executable is _not_ making such assumptions. – MSalters Dec 08 '20 at 10:28
  • @MSalters `I think you missed the point of my comment.` I see. I had thought that the point of your comment was that you didn't get the relevance of the linked article. I had also assumed that the peculiarities of x86 wouldn't have been an example of a case where null would compare greater than another pointer and had thus regarded it as irrelevant to the topic at hand. Those assumptions lead to how I interpreted your comment. – eerorika Dec 08 '20 at 10:39
  • Well, it's not a real x86 pecularity. ARM has `LO Unsigned lower` and `LT signed less than` condition codes. Hence, it's up to the compiler developer to choose which condition code is used when comparing pointers. Choose `LT` and `nullptr` is greater than half the address space. You'd have to look at the 68000 to have an ISA with true pointer registers, and it indeed has a hardware prescribed comparison. `cmpa` is a signed comparison, and again half of the pointers are smaller than all-bits-zero. – MSalters Dec 08 '20 at 10:51
  • 7
    I have a platform that has addressable memory at 0x00000000 and at 0x40000000, and that uses 0x20000000 as `nullptr`. Comparing pointers with `<` would sort all RAM pointers before `nullptr`, and all ROM pointers after it. – Simon Richter Dec 08 '20 at 11:06
  • eerorika: high-half kernels are not particular to x86. It's totally normal for the kernel to "own" addresses with the high bit set. But as the name "high half" implies, we think of that address range as *above* user-space, not negative. I'm surprised by @MSalters's argument; I thought it was normal to treat pointers as unsigned integers. IDK, maybe old Windows compilers used to be different. OTOH, I suggested using signed compares myself in [an answer for x86-64](//stackoverflow.com/q/47687805), but that's predicated on there being a range of unusable (non-canonical) addresses. – Peter Cordes Dec 08 '20 at 12:45
  • 1
    https://wiki.osdev.org/Higher_Half_Kernel mentions that this is not x86-specific. – Peter Cordes Dec 08 '20 at 12:47
  • Might be my start with assembler on MSX, which has its ROM pages in the **low** 32 kB and RAM in the high 32 kB. Still, my point is that the MSB is often used as a meaningful flag, and that treating pointers as signed makes the test easy. – MSalters Dec 08 '20 at 13:50
  • I think your introductory summary is at best misleading. _"Can you compare nullptr to other pointers"_ Yes _"for order?"_ No (as you then explain) – Asteroids With Wings Dec 08 '20 at 15:30
  • @AsteroidsWithWings I think the first sentence is quite clear that the comparison isn't necessarily useful. – eerorika Dec 08 '20 at 15:52
  • Are there any systems where memory allocations are laid out backwards? You'd probably find nullptr would be the largest on those. – OrangeDog Dec 08 '20 at 17:09
  • "Can you drive to the bank to do your grocery shopping?" "Yes, but it isn't useful". Basically what you're saying I think most people would propose that "no" is a clearer answer. – Asteroids With Wings Dec 08 '20 at 17:41
  • @jackX Good point. I've edited the answer. – eerorika Dec 09 '20 at 08:27
  • @eerorika IMHO, compare a pointer and `nullptr` may not go into the bullet you give in your answers at all. Because `nullptr` is a null pointer constant. It violates the precondition "If both operands are pointers", so "pointer conversions and qualification conversions are performed to bring them to their composite pointer type" will not apply to it. hence it violates "After conversions, the operands shall have the same type.". [Clang](https://godbolt.org/z/3eYasP) gives the error. So "Can you compare nullptr to other pointers for order?" No – xmh0511 Dec 09 '20 at 08:27
14

No. Less-than comparisons involving a nullptr do not have specified behavior, and while they do not involve undefined behavior the results are not even guaranteed to be consistent.

The guarantees provided by < on pointers are extremely limited. Even comparing two separately heap-allocated objects is not guaranteed to be consistent (for that you need std::less, which will consistently place a null pointer somewhere in the ordering but not at a standard-defined place). The best you can say is that no pointer to an object will compare equal to a nullptr.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
  • In real life every compiler is going to use a simple integer comparison for pointers because that's the easiest way to satisfy the requirements. – OrangeDog Dec 08 '20 at 17:09
  • 3
    In real life it's almost always more pragmatic to follow the rules of the language than to try to guess what'll be safe. – Sneftel Dec 08 '20 at 17:17
4

nullptr is always smaller than other pointers for operator<

No, compare a nullptr with a pointer by a relational operator is not supported by the standard.

To compare the operands of a relational operator, the following rule will first be applied to both operands, that is expr.rel#2

The usual arithmetic conversions are performed on operands of arithmetic or enumeration type. If both operands are pointers, pointer conversions and qualification conversions are performed to bring them to their composite pointer type. After conversions, the operands shall have the same type.

nullptr is not a pointer, instead, it is called a null pointer constant. So, "pointer conversions and qualification conversions are performed to bring them to their composite pointer type" will not apply to it. So, it violates, After conversions, the operands shall have the same type.

Clang gives a correct diagnosis. Since the code is ill-formed, hence talk about what's the result does not make sense.

xmh0511
  • 7,010
  • 1
  • 9
  • 36
  • If only our compiler gave that error, then I wouldn't have to ask the question. – stefaanv Dec 09 '20 at 12:10
  • @srefaanv Personally, I think clang's implementation is nearest to what the standard required and the diagnosis it give is more simple to read. – xmh0511 Dec 09 '20 at 12:34
  • I agree, that's why I accepted your answer. I just can't use clang in my current long-running project. We changed the code based on the answers of this question. – stefaanv Dec 09 '20 at 13:17