5

The C++ standard says that subtracting pointers from non-array elements is UB:

int a, b;
&a - &b; // UB, since pointers don't point to the same array.

But if both pointers are casted to uintptr_t, then both exressions are no longer pointer expressions and subtracting them seems to be legal from the Standard's perspective:

int a, b;
reinterpret_cast<uintptr_t>(&a) - reinterpret_cast<uintptr_t>(&b); 

Is this correct or am I missing something?

Bikineev
  • 1,685
  • 15
  • 20
  • 10
    You get an integer, the value of which is entirely implementation defined, so you can't rely on it for much. Doesn't seems very useful still, even though the UB is eliminated. – StoryTeller - Unslander Monica Mar 04 '20 at 13:16
  • Fully agree with @StoryTeller-UnslanderMonica. Just to give a hint on more discussion around a possible similar topic, [here is another post](https://stackoverflow.com/questions/13181913/c-iterating-a-struct) asking how to iterate over members of a struct. – diogoslima Mar 04 '20 at 13:22
  • 1
    Note that `uintptr_t` is optional. It may not be provided by your implemenation. – Daniel Langr Mar 04 '20 at 13:25

3 Answers3

2

The subtraction of the integers is legal in the sense that behaviour is not undefined.

But the standard doesn't technically have guarantees about the values of the converted integers, and consequently you have no guarantees about the value of result of the subtraction either (except in relation to the unspecified integer values) - you could get a large value or a small value (or if the pointers had non-interconvertible types, then subtraction of converted intergers of different objects could even yield zero) depending on the system.

Furthermore, if you did some other pointer arithmetic that would result in a pointer value i.e. convert pointer to integer and add offset, then there is technically no guarantee that that converting the result of the addition back to a pointer type would yield the pointer value at that offset from the original. But it would probably work (assuming there actually exists an object of correct type at that address) except perhaps on systems using segmented memory or something more exotic.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • I don't think a zero difference would be valid in OP's specific case. Casting the integral value back to a pointer must yield the original pointer value and if the two integral representations were identical, then the two pointer values would need to be identical up to type as well, but they are of same type, so that can't work. – walnut Mar 04 '20 at 14:08
  • 1
    Actually the standard does make some guarantees about the converted integers: A pointer can be converted to any integral type large enough to hold all values of its type. And any such value can be converted back to the same pointer. (But there's no guarantee which integer a pointer will be converted to. e.g. you can convert a pointer to an integer and back and you'll get the value you started with, but you can't convert an integer to a pointer and back and be guaranteed to get the same integer you started with.) (* I am not a language lawyer.) – Wyck Mar 04 '20 at 14:23
  • @walnut you are probably correct. Although not explicitly stated, the integers couldn't possibly have the same value in this case. What I was trying to convey that even same address is that not pointing to the same object is not sufficient for the converted value to be different. But given that the pointer types are same (and thus interconvertible), the integer values would have to be different in practice. I changed the wording. – eerorika Mar 04 '20 at 14:51
1

You are correct.

The behaviour on subtracting pointers that do not point to elements of the same array is undefined. For this purpose a pointer is allowed to be one beyond the final element of an array, and an object counts as an array of length one.

But once you cast a pointer to a suitable type, e.g. to a std::uintptr_t (assuming your compiler supports it; it doesn't have to) you can apply any arithmetic you want to it, subject to the constrants imposed upon you by that type.

Although such rules may seem obtuse, they are related to a similar rule where you are not allowed to read a pointer that doesn't point to valid memory. All of this helps achieve greater portability of the language.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
0

The UB allows an implementation to do anything. It does not prevent an implementation to simply compute the difference between address values and divide by the size. But it allows an implementation that would control whether both elements are member of the same array (or point one past end of the array) to raise an exception or crash.

Requiring both pointers to point to the same array allows the implementation to assume that any value between that (and correctly aligned...) is also valid and points inside the same array.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • According to my knowledge, there is no UB in the second code snippet. – Bikineev Mar 04 '20 at 15:19
  • @Bikineev: there is no pointer substraction in second snippet. Each pointer is converted to an int and code only substract ints, so no UB is involved. But there is no guarantee that all the memory between both addresses is available for the program. – Serge Ballesta Mar 04 '20 at 15:22