Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can std::uintptr_t be used to avoid undefined behavior of out-of-bounds pointer arithmetic?

Now we know that doing out-of-bounds-pointer-arithmetic has undefined behavior as described in this SO question.

My question is: can we workaround such restriction by casting to std::uintptr_t for arithmetic operations and then cast back to pointer? is that guaranteed to work?

For example:

char a[5];
auto u = reinterpret_cast<std::uintptr_t>(a) - 1;
auto p = reinterpret_cast<char*>(u + 1); // OK?

The real world usage is for optimizing offsetted memory access -- instead of p[n + offset], I want to do offset_p[n].

EDIT To make the question more explicit:

Given a base pointer p of a char array, if p + n is a valid pointer, will reinterpret_cast<char*>(reinterpret_cast<std::uintptr_t>(p) + n) be guaranteed to yield the same valid pointer?

like image 778
Jamboree Avatar asked Mar 25 '14 02:03

Jamboree


2 Answers

No, uintptr_t cannot be meaningfully used to avoid undefined behavior when performing pointer arithmetic.

For one thing, at least in C there is no guarantee that uintptr_t even exists. The requirement is that any value of type void* may be converted to uintptr_t and back again, yielding the original value without loss of information. In principle, there might not be any unsigned integer type wide enough to hold all pointer values. (I presume the same applies to C++, since C++ inherits most of the C standard library and defines it by reference to the C standard.)

Even if uintptr_t does exist, there is no guarantee that a given arithmetic operation on a uintptr_t value does the same thing as the corresponding operation on a pointer value.

For example, I've worked on systems (Cray vector systems, T90 and SV1) on which byte pointers are implemented in software. A native address is a 64-bit address that refers to a 64-bit word; there is no hardware support for byte addressing. A char* or void* pointer consists of a word pointer with a 3-bit offset stored in the otherwise unused high-order bits. Conversion between integers and pointers simply copies the bits. So incrementing a char* would advance it to point to the next 8-bit byte in memory; incrementing a uintptr_t obtained by converting a char* would advance it to point to the next 64-bit word.

That's just one example. More generally, conversions between pointers and integers are implementation-defined, and the language standard makes no guarantee about the semantics of those conversions (other than, in some cases, converting back to a pointer).

So yes, you can convert a pointer value to uintptr_t (if that type exists) and perform arithmetic on it without risking undefined behavior -- but the result may or may not be meaningful.

It happens that, on most systems, the mapping between pointers and integers is simpler, and you probably can get away with that kind of game. But you're better off using pointer arithmetic directly, and just being very careful to avoid any invalid operations.

like image 120
Keith Thompson Avatar answered Sep 28 '22 10:09

Keith Thompson


Yes, that is legal, but you must reinterpret_cast exactly the same uintptr_t value back to char*.

(Therefore, what it you're intending to do is illegal; that is, converting a different value back to a pointer.)

5.2.10 Reinterpret cast

4 . A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined.

5 . A value of integral type or enumeration type can be explicitly converted to a pointer. A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value;

(Note that there'd be no way, in general, for the compiler to know that you subtracted one and then added it back.)

like image 44
Jesse Beder Avatar answered Sep 28 '22 08:09

Jesse Beder