简体   繁体   English

C ++:将指针转换为int并稍后再返回指针是否安全?

[英]C++: Is it safe to cast pointer to int and later back to pointer again?

Is it safe to cast pointer to int and later back to pointer again? 将指针转换为int并稍后再返回指针是否安全?

How about if we know if the pointer is 32 bit long and int is 32 bit long? 如果我们知道指针是否为32位长且int是32位长怎么样?

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? 这是否包含在标准中?

No. 没有。

For instance, on x86-64, a pointer is 64-bit long, but int is only 32-bit long. 例如,在x86-64上,指针长度为64位,但int只有32位长。 Casting a pointer to int and back again makes the upper 32-bit of the pointer value lost. 将指针转换为int并再次返回会使指针值的高32位丢失。

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. 如果你想要一个保证与指针一样长的整数类型,你可以在<cstdint>使用intptr_t类型。 You could safely reinterpret_cast from a pointer to an intptr_t and back. 您可以安全地从指向intptr_t并返回的指针reinterpret_cast。

Yes, if... (or "Yes, but...") and no otherwise. 是的,如果...... (或“是的,但......”),否则没有。

The standard specifies (3.7.4.3) the following: 该标准规定了(3.7.4.3)以下内容:

  • 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 指针值是一个安全派生的指针[...]如果它是一个明确定义的指针转换或reinterpret_cast的安全派生指针值的结果[或]一个安全的整数表示的reinterpret_cast的结果 - 派生指针值
  • 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 整数值是安全派生指针的整数表示,如果其类型至少与std::intptr_t一样大,并且[...]是安全派生指针值的reinterpret_cast的结果[或者]安全派生指针值的整数表示的有效转换的结果[或]加法或按位运算的结果,其操作数之一是安全派生指针值的整数表示
  • A traceable pointer object is [...] an object of an integral type that is at least as large as std::intptr_t 可跟踪指针对象是整数类型的对象,至少与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 (ie "safe") to cast to your integer type and back. 因此, 如果您首先使用std::intptr_t (最喜欢的事情!),或者您知道您使用的任何整数类型(比如long )的存储大小至少是std::intptr_t的大小std::intptr_t ,然后它是允许的和明确定义的(即“安全”)转换为您的整数类型并返回。 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; 一个有趣的轶事是C ++标准根本没有直接定义std::intptr_t ; it merely says "the same as 7.18 in the C standard" . 它只是说“与C标准中的7.18相同”

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" . 另一方面,C标准声明“指定一个带符号的整数类型,其属性是任何有效的void指针都可以转换为这种类型,然后转换回指向void的指针,结果将等于原始指针“
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* . 这意味着,如果没有上面相当复杂的定义(特别是第一个项目符号点的最后一位),除了void*不允许转换为/来自任何东西。

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. 因此,在一般情况下,它不是“安全的”,因为一般情况下int很容易变得太小。 In your specific case it though it might be safe, since your int might be sufficiently large to store your pointer. 在您的特定情况下它虽然可能是安全的,因为您的int可能足够大以存储您的指针。

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. 通常,当您需要执行类似的操作时,应使用intptr_t / uintptr_t类型,这些类型是专门为此目的引入的。 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. 不幸的是, intptr_t / uintptr_t不是当前C ++标准的一部分(它们是标准的C99类型),但是许多实现仍然提供它们。 You can always define these types yourself, of course. 当然,您可以自己定义这些类型。

In general, no; 一般来说,没有; pointers may be larger than int , in which case there's no way to reconstruct the value. 指针可能大于int ,在这种情况下,无法重建值。

If an integer type is known to be large enough, then you can; 如果已知整数类型足够大,则可以; according to the Standard (5.2.10/5): 根据标准(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 ++ 03中,没有标准的方法来判断哪些整数类型足够大。 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. C ++ 11和C99(以及实际上大多数 C ++ 03实现)以及Boost.Integer为此目的定义了intptr_tuintptr_t 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* . 或者,如果您没有特殊原因使其成为整数类型,请使用void*

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). 当然,如果一个int太小而不能保存完整的指针值并截断,你就不会得到原来的指针(希望你的编译器会警告你这种情况,GCC截断从指针到整数的转换是硬错误)。 A long , or uintptr_t if your library supports it, may be better choices. 如果你的库支持它,那么long或者uintptr_t可能是更好的选择。

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. 特别是,如果你在程序中使用垃圾收集器,它可能很容易决定指针不再是未完成的,当你稍后将整数转换回指针并尝试取消引用它时,你会发现对象已经收获了。

No, it is not (always) safe (thus not safe in general). 不,它不是(总是)安全的(因此通常不安全)。 And it is covered by the standard. 由标准的覆盖。

ISO C++ 2003, 5.2.10: 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;
}

Absolutely not. 绝对不。 Doing some makes a bad assumption that the size of an int and a pointer are the same. 做一些假设一个int和指针的大小是相同的坏假设。 This is almost always no the case on 64 bit platforms. 在64位平台上几乎总是如此。 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

To an int ? 到一个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. 并不总是如果你在64位机器上,那么int只有4个字节,但是指针长度为8个字节,因此当你从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. 您可以简单地使用8字节长的数据类型,无论您是否在32/64位系统上都可以使用,例如unsigned long long unsigned,因为您不希望在32位系统上进行符号扩展。

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. 值得注意的是,在Linux上, unsigned long总是指针大小*所以如果你的目标是Linux系统,你可以使用它。

*According to cppreference and also tested it myself but not on all Linux and Linux like systems *根据cppreference并自己测试,但不是在所有Linux和Linux系统上测试

如果问题是你想对它进行正常的数学运算,可能最安全的做法是将它转换为指向char的指针(或者更好的是, * uint8_t ),做你的数学计算,然后再把它强制转换。

Use uintptr_t from "stdint.h" or from "boost/stdint.h". 使用“stdint.h”或“boost / stdint.h”中的uintptr_t。 It is guaranteed to have enough storage for a pointer. 保证有足够的存储空间用于指针。

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. C ++中的指针可以有三种类型:near,far和huge。 They have different sizes. 它们有不同的尺寸。 And if we talk about an integer its normally of 16 or 32 bit. 如果我们谈论一个通常为16或32位的整数。 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. 在大多数情况下,整数将缺少存储指针的空间,从而导致价值损失。

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. 如果你要进行任何系统便携式转换,你需要使用像Microsofts INT_PTR / UINT_PTR之类的东西,之后的安全依赖于目标平台以及你打算对INT_PTR做什么。 generally for most arithmatic char* or uint_8* works better while being typesafe(ish) 通常对于大多数算术char *或uint_8 *在类型安全(ish)时工作得更好

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM