27

Is it safe to cast pointer to int and later back to pointer again?

How about if we know if the pointer is 32 bit long and int is 32 bit long?

long* juggle(long* p) {
    static_assert(sizeof(long*) == sizeof(int));
    int v = reinterpret_cast<int>(p); // or if sizeof(*)==8 choose long here
    do_some_math(v); // prevent compiler from optimizing
    return reinterpret_cast<long*>(v);
}

int main() {
    long* stuff = new long(42);
    long* ffuts = juggle(stuff); 
    std::cout << "Is this always 42? " << *ffuts << std::endl;
}

Is this covered by the Standard?

towi
  • 21,587
  • 28
  • 106
  • 187
hasdf
  • 699
  • 2
  • 7
  • 8
  • 12
    Why not cast to void* – Martin York Aug 25 '10 at 16:17
  • Why would you want to? What can you do with an int you can't do with a pointer? – Dlongnecker Aug 25 '10 at 16:42
  • 1
    possible duplicate of [sizeof (int) == sizeof (void\*)?](http://stackoverflow.com/questions/502811/sizeof-int-sizeof-void) – Amro Jul 26 '13 at 04:13
  • @Amro this question goes beyond `sizeof` – towi Jul 26 '13 at 07:18
  • @towi: with your edit and added example, it became a different question... I take my close-vote back. – Amro Jul 26 '13 at 11:48
  • Dunno about C++, but in C it's safe to cast a pointer to int. The only int it's safe (on all compliant C platforms) to cast to a pointer (and achieve "defined" results) is zero (null). I know this because ca 1990 we had a C compiler that had to tip-toe around this certification issue. – Hot Licks Jul 26 '13 at 12:09
  • 1
    @HotLicks: In C it is _not always_ safe to cast a pointer to `int`, either. _"A pointer may be converted to an integral type. The size of integer required and the result are implementation-defined. If the space provided is not long enough, the behavior is undefined"_ (ISO C90 6.3.4). – alecov Jul 26 '13 at 12:21
  • @Alek - So what unsafe behavior can occur as a result? (Loss of precision is not "unsafe".) – Hot Licks Jul 26 '13 at 14:00
  • 1
    @HotLicks: The unsafe behavior that can occur is the worst of them all: _undefined behavior_. (In practice, however, only a loss of precision will occur, but this is enough to cause a segmentation fault if the integer is cast back to a pointer, and then dereferenced.) – alecov Jul 26 '13 at 14:07
  • @Alek - But casting integer to pointer *is* unsafe, except in limited circumstances. Casting pointer to integer, even when truncation occurs, is sometimes useful for generating hash values, etc, and hence, while the transformation is not "defined", is not "unsafe". – Hot Licks Jul 26 '13 at 14:15
  • 1
    @HotLicks: Once more, _in practice_, you're correct and this is not an issue with most, if not all, compilers and processors, but regarding _standards_, which is what the question is about, _undefined behavior is **definitely** unsafe behavior_. A program which invokes UB is a non-compliant program, and, under the coverage of ISO 9899, no assertion can be made regarding its behavior. For example, casting a pointer to a (signed) integer might overflow the integer (which is another UB), mess with some padding bits and create a trap representation. – alecov Jul 26 '13 at 14:28
  • I can't speak to the precise wording of the current standard, but ca 1990, when we were helping get a C compiler certified, the behavior from pointer to int was not regarded as "unsafe", and was required to be supported (though the mapping was not defined). Going the other way, only the single value zero needed to be "defined"/supported. This came up because, on the System/38, pointers were actually "capabilities" containing a 48-bit address and a 1-bit "tag". The "tag" was inaccessible to software, and was necessarily lost casting to int. So much study of the standard ensued. – Hot Licks Jul 26 '13 at 21:32
  • @HotLicks: Requiring a compiler to support some undefined behavior construction is certainly undue; however, to better understand the situation you've mentioned, some further context must be provided. Nonetheless, note that a conformant _compiler_ ("implementation" in standard jargon) can choose to actively define, and even document, any UB condition as it wish, as if it were some sort of extension or implementation-defined behavior; a conformant _program_, however, simply cannot rely on such behavior, not even by accident, putting its "conformability" at risk if it does. – alecov Jul 30 '13 at 11:58

12 Answers12

37

No.

For instance, on x86-64, a pointer is 64-bit long, but int is only 32-bit long. Casting a pointer to int and back again makes the upper 32-bit of the pointer value lost.

You may use the intptr_t type in <cstdint> if you want an integer type which is guaranteed to be as long as the pointer. You could safely reinterpret_cast from a pointer to an intptr_t and back.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 1
    Well, is it safe to cast if it is known that the pointer is 32-bit long? – hasdf Aug 25 '10 at 16:06
  • @RichN - my understanding is that only minimum ranges are standardized. For Visual C++ for example, size of int is the same on 32- and 64-bit platforms - 64-bit int is defined as __int64. See http://msdn.microsoft.com/en-us/library/296az74e(VS.80).aspx for details. – Steve Townsend Aug 25 '10 at 16:14
  • 1
    @hasdf technically i'd imagine that would depend on the implementation, but i'd expect most implemenatations to do what you want if the sizes where the same. what you can definitely do is cast to/from an appropriate sized array of chars. of course why you want to do this is also important cos there could be even more UB lurking in what you are trying to use this for. – jk. Aug 25 '10 at 16:14
  • @hasdf Well, then you limit the portability of your app. @RichN I guess sizeof(int) depends on the compiler - see http://stackoverflow.com/questions/589575/c-size-of-int-long-etc – Sundar Aug 25 '10 at 16:19
  • 6
    @jk: No, the standard specifies that you get the original value back if the integer type is large enough. The only problem is finding a type large enough. – Mike Seymour Aug 25 '10 at 16:37
  • 11
    @hasdf: Use an `intptr_t` if you really need to cast to an arithmetic type. – kennytm Aug 25 '10 at 16:47
26

Yes, if... (or "Yes, but...") and no otherwise.

The standard specifies (3.7.4.3) the following:

  • A pointer value is a safely-derived pointer [...] if it is the result of a well-defined pointer conversion or reinterpret_cast of a safely-derived pointer value [or] the result of a reinterpret_cast of an integer representation of a safely-derived pointer value
  • An integer value is an integer representation of a safely-derived pointer [...] if its type is at least as large as std::intptr_t and [...] the result of a reinterpret_cast of a safely-derived pointer value [or] the result of a valid conversion of an integer representation of a safely-derived pointer value [or] the result of an additive or bitwise operation, one of whose operands is an integer representation of a safely-derived pointer value
  • A traceable pointer object is [...] an object of an integral type that is at least as large as std::intptr_t

The standard further states that implementations may be relaxed or may be strict about enforcing safely-derived pointers. Which means it is unspecified whether using or dereferencing a not-safely-derived pointer invokes undefined behavior (that's a funny thing to say!)

Which alltogether means no more and no less than "something different might work anyway, but the only safe thing is as specified above".

Therefore, if you either use std::intptr_t in the first place (the preferrable thing to do!) or if you know that the storage size of whatever integer type you use (say, long) is at least the size of std::intptr_t, then it is allowable and well-defined (i.e. "safe") to cast to your integer type and back. The standard guarantees that.

If that's not the case, the conversion from pointer to integer representation will probably (or at least possibly) lose some information, and the conversion back will not give a valid pointer. Or, it might by accident, but this is not guaranteed.

An interesting anecdote is that the C++ standard does not directly define std::intptr_t at all; it merely says "the same as 7.18 in the C standard".

The C standard, on the other hand, states "designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer".
Which means, without the rather complicated definitions above (in particular the last bit of the first bullet point), it wouldn't be allowable to convert to/from anything but void*.

Damon
  • 67,688
  • 20
  • 135
  • 185
21

Yes and no.

The language specification explicitly states that it is safe (meaning that in the end you will get the original pointer value) as long as the size of the integral type is sufficient to store the [implementation-dependent] integral representation of the pointer.

So, in general case it is not "safe", since in general case int can easily turn out to be too small. In your specific case it though it might be safe, since your int might be sufficiently large to store your pointer.

Normally, when you need to do something like that, you should use the intptr_t/uintptr_t types, which are specifically introduced for that purpose. Unfortunately, intptr_t/uintptr_t are not the part of the current C++ standard (they are standard C99 types), but many implementations provide them nevertheless. You can always define these types yourself, of course.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
5

In general, no; pointers may be larger than int, in which case there's no way to reconstruct the value.

If an integer type is known to be large enough, then you can; according to the Standard (5.2.10/5):

A pointer converted to an integer of sufficient size ... and back to the same pointer type will have its original value

However, in C++03, there's no standard way to tell which integer types are large enough. C++11 and C99 (and hence in practice most C++03 implementations), and also Boost.Integer, define intptr_t and uintptr_t for this purpose. Or you could define your own type and assert (preferably at compile time) that it's large enough; or, if you don't have some special reason for it to be an integer type, use void*.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
3

Is it safe? Not really.

In most circumstances, will it work? Yes

Certainly if an int is too small to hold the full pointer value and truncates, you won't get your original pointer back (hopefully your compiler will warn you about this case, with GCC truncating conversions from pointer to integers are hard errors). A long, or uintptr_t if your library supports it, may be better choices.

Even if your integer type and pointer types are the same size, it will not necessarily work depending on your application runtime. In particular, if you're using a garbage collector in your program it might easily decide that the pointer is no longer outstanding, and when you later cast your integer back to a pointer and try to dereference it, you'll find out the object was already reaped.

Jack Lloyd
  • 8,215
  • 2
  • 37
  • 47
2

No, it is not (always) safe (thus not safe in general). And it is covered by the standard.

ISO C++ 2003, 5.2.10:

  1. A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined.
  2. 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; mappings between pointers and integers are otherwise implementation-defined.

(The above emphases are mine.)

Therefore, if you know that the sizes are compatible, then the conversion is safe.

#include <iostream>

// C++03 static_assert.
#define ASSURE(cond) typedef int ASSURE[(cond) ? 1 : -1]

// Assure that the sizes are compatible.
ASSURE(sizeof (int) >= sizeof (char*));

int main() {
    char c = 'A';
    char *p = &c;
    // If this program compiles, it is well formed.
    int i = reinterpret_cast<int>(p);
    p = reinterpret_cast<char*>(i);
    std::cout << *p << std::endl;
}
alecov
  • 4,882
  • 2
  • 29
  • 55
2

Absolutely not. Doing some makes a bad assumption that the size of an int and a pointer are the same. This is almost always no the case on 64 bit platforms. If they are not the same a precision loss will occur and the final pointer value will be incorrect.

MyType* pValue = ...
int stored = (int)pValue; // Just lost the upper 4 bytes on a 64 bit platform
pValue = (MyType*)stored; // pValue is now invalid 
pValue->SomeOp();  // Kaboom
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
0

To an int ? not always if you are on a 64 bit machine then int is only 4 bytes, however pointers are 8 bytes long and thus you would end up with a different pointer when you cast it back from int.

There are however ways to get around this. You can simply use an 8 byte long data type ,which would work whether or not you are on 32/64 bit system, such as unsigned long long unsigned because you don't want sign extension on 32-bit systems.

It is important to note that on Linux unsigned long will always be pointer size* so if you are targeting Linux systems you could just use that.

*According to cppreference and also tested it myself but not on all Linux and Linux like systems

A. H.
  • 942
  • 10
  • 20
0

If the issue is that you want to do normal math on it, probably the safest thing to do would be to cast it to a pointer to char (or better yet, * uint8_t), do your math, and then cast it back.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
0

Use uintptr_t from "stdint.h" or from "boost/stdint.h". It is guaranteed to have enough storage for a pointer.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

No it is not. Even if we rule out the architecture issue, size of a pointer and an integer have differences. A pointer can be of three types in C++ : near, far, and huge. They have different sizes. And if we talk about an integer its normally of 16 or 32 bit. So casting integer into pointers and vice-verse is not safe. Utmost care has to be taken, as there very much chances of precision loss. In most of the cases an integer will be short of space to store a pointer, resulting in loss of value.

0

If your going to be doing any system portable casting, you need to use something like Microsofts INT_PTR/UINT_PTR, the safety after that relies on the target platforms and what you intend doing to the INT_PTR. generally for most arithmatic char* or uint_8* works better while being typesafe(ish)

Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • The _PTR in the Microsoft/Intel world does not always mean pointer. In the case you are quoting, it means parameter. In fact, if you look at the reference you are quoting, none of the _PTRs are pointers: they are all parameters. – cup Jun 30 '13 at 17:42
  • 1
    @cup: they are integer representations of pointers, guaranteed to hold the full width of a pointer. on top of that, PTR here means pointer, if you actually read the description of INT_PTR, you'll see that: `A signed integer type for pointer precision. Use when casting a pointer to an integer to perform pointer arithmetic.` – Necrolis Jul 01 '13 at 11:09
  • I may be wrong but I'm convinced that the documentation is flawed. It may be a joke but look at the definition of HALF_PTR. Does that make sense: can you have half a pointer? Now look at PINT_PTR or PHALF_PTR. Why would anyone have two naming conventions on one type? The normal MS convention is to either have a leading capital P or multicase Ptr (used in .net). If it is a pointer to a pointer then it would have a PP prefix or a _PTR_PTR suffix (if _PTR really does mean pointer). I wouldn't expect a P prefix and a _PTR suffix. – cup Jul 01 '13 at 19:53
  • 1
    @cup: `HALF_PTR` is used for alignment, ie: if I have a struct that must hold two small numbers and a pointer, and must be word aligned on each platform, a pointer + 2 half pointers will allow that. the _PTR comes from the stdint convention (also they are integers, not pointers, thus P would be incorrect in the SAL style). – Necrolis Jul 02 '13 at 11:15