1

Given that intptr_t is optional and ptrdiff_t is mandatory, would p - nullptr be a good substitute for (intptr_t)p, to be converted back from a result denoted by n with nullptr + n instead of (decltype(p))n? It's IIUC semantically equivalent on implementations with intptr_t defined, but also works as intended otherwise.

If I'm right in the above, why does the standard allow not implementing intptr_t? It seems the liberty thus afforded isn't particularly valuable, just a set of two simple local source code transforms (or an optimized equivalent) to shave off.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
ByteEater
  • 885
  • 4
  • 13
  • 2
    You have an implicit assumption about the validity of `p - nullptr`. It's unfounded. – StoryTeller - Unslander Monica Feb 21 '22 at 17:05
  • 1
    If `intptr_t` is not supported, that is a good sign that it is simply impossible to represent all addresses (sensibly) as some fundamental integral type. For why this is allowed, see [this question](https://stackoverflow.com/questions/53380203/why-are-uintptr-t-and-intptr-t-optional-types-in-the-c-and-c-standard). – user17732522 Feb 21 '22 at 17:13

1 Answers1

6

No. ptrdiff_t only needs to be large enough to encompass a single object, not the entire memory space. And (char*)p - (char*)nullptr causes undefined behavior if p is not itself a null pointer.

p - nullptr without the casts is ill-formed.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    Technically, there are no guarantees for `ptrdiff_t` to even encompass all single objects. Subtracting such pointers that the result would exceed `ptrdiff_t` is simply UB. – eerorika Feb 21 '22 at 17:10
  • @eerorika: There are no guarantees made *by the C++ standard*, as the definition of `ptrdiff_t` redirects to the C Standard. So the note about results exceeding the range of `ptrdiff_t` is a hypothetical scenario in case the C standard breaks the `ptrdiff_t` definition. – Ben Voigt Feb 21 '22 at 17:13
  • 2
    @BenVoigt The C standard already allows for the same though: https://port70.net/~nsz/c/c11/n1570.html#6.5.6p9 and https://port70.net/~nsz/c/c11/n1570.html#7.19p2 has nothing more to say about it than that it is the type of subtracted pointers. – user17732522 Feb 21 '22 at 17:17
  • Oh my… It means that if an array has more than `PTRDIFF_MAX` elements, pointers to its elements cannot be safely subtracted with `-`. So if I have two such pointers, the most efficient safe way to subtract them or to determine that they're too far away for it seems to be a series of logarithmic length of calls to `std::midpoint` and pointer comparisons followed by shifting `PTRDIFF_MAX` to the right by the number of steps and comparing to `1`, right? – ByteEater Feb 21 '22 at 17:45
  • 1
    @ByteEater: If a single array has more than `PTRDIFF_MAX` elements, your compiler has a broken `ptrdiff_t` (all library functions including `std::midpoint` may be expected to fail on an array larger than `PTRDIFF_MAX`). But different arrays can easily be separated by more than `PTRDIFF_MAX` bytes, and you can't subtract pointers to different arrays at all (read the links in my answer) – Ben Voigt Feb 21 '22 at 17:48
  • Ben, "... if p is not itself a null pointer" --> sure that `(char*)nullptr - (char*)nullptr` is OK? – chux - Reinstate Monica Feb 21 '22 at 17:53
  • 2
    @chux-ReinstateMonica Yes thats ok. There is a specific case for that in the standard (https://eel.is/c++draft/expr.add#5.1) Similarly adding `0` to a null pointer value is allowed. I think C however does not allow either of these, which I noticed breaks a lot of code when compiled with `-fsanitize=undefined`, because it would technically require special casing in many functions accepting a pointer+length in case both are null. – user17732522 Feb 21 '22 at 18:11
  • @BenVoigt, isn't `SIZE_MAX` the maximum size of arrays? Which can be greater than `PTRDIFF_MAX`, right? And are you sure the standard allows `std::midpoint` to fail in such cases (which would AFAIK leave no efficient way to subtract pointers to positions of a large array, even if they're actually close, say 1000 units apart – the last resort would be to increment the smaller one until it equals the bigger and count the steps, which takes linear time)? The note in //en.cppreference.com/w/cpp/numeric/midpoint seems to indicate that it's by design provided to handle that. – ByteEater Feb 21 '22 at 20:45
  • @ByteEater: The maximum size of an object can't be greater than `SIZE_MAX` but it doesn't have to be equal either. If the maximum size of an object falls between **(`PTRDIFF_MAX`, `SIZE_MAX`]** then things are broken, and it may very well be true that measuring the distance between arbitrary pointers in a large object is a linear-time operation under such broken assumptions. For things to not be broken, the maximum size of an object can't be greater than `PTRDIFF_MAX` – Ben Voigt Feb 21 '22 at 21:24
  • Do you see any problems with my approach 6 comments above which executes only a logarithmic number of statements, @BenVoigt? – ByteEater Feb 21 '22 at 21:39
  • @ByteEater: I see a problem with your claim that a logarithmic number of calls to `std::midpoint` implies a logarithmic number of operations. I see no requirement in the C++ Standard that `std::midpoint` is constant-time. On the hypothetical evil platform where `PTRDIFF_MAX` is chosen smaller than the maximum number of elements in an array, I expect that the performance of `std::midpoint` is evil. – Ben Voigt Feb 21 '22 at 21:50
  • @ByteEater: I also don't see how that has anything to do with the question. You can't call `std::midpoint` on `nullptr` (cast to any pointer type at all). – Ben Voigt Feb 21 '22 at 21:55