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

时间:2010-08-25 16:02:34

标签: c++ pointers

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

如果我们知道指针是否为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;
}

这是否包含在标准中?

12 个答案:

答案 0 :(得分:35)

没有

例如,在x86-64上,指针长度为64位,但int只有32位长。将指针转换为int并再次返回会使指针值的高32位丢失。

如果你想要一个保证与指针一样长的整数类型,你可以在intptr_t中使用<cstdint>类型。您可以安全地从指向intptr_t并返回的指针重新解释。

答案 1 :(得分:21)

是和否。

语言规范明确指出它是安全的(意味着最终你将获得原始指针值),只要整数类型的大小足以存储指针的[依赖于实现的]整数表示形式

因此,在一般情况下,它不是“安全的”,因为一般情况下int很容易变得太小。在您的特定情况下它虽然可能是安全的,因为您的int可能足够大以存储您的指针。

通常,当您需要执行此类操作时,应使用intptr_t / uintptr_t类型,这些类型是专门为此目的而引入的。不幸的是,intptr_t / uintptr_t不是当前C ++标准的一部分(它们是标准的C99类型),但许多实现仍然提供它们。当然,您可以自己定义这些类型。

答案 2 :(得分:21)

是的,如果... (或“是的,但是......”),否则没有。

该标准规定了(3.7.4.3)以下内容:

  • 指针值是安全派生的指针[...],如果它是定义良好的指针转换的结果或安全派生的指针值的reinterpret_cast [或] {的结果{1}}安全派生指针值的整数表示
  • 整数值是安全派生指针[...]的整数表示,如果其类型至少与reinterpret_cast一样大且[...] std::intptr_t的结果安全派生的指针值[或] 有效转换安全派生指针值的整数表示的结果[或]加法或按位运算的结果,其操作数之一是a的整数表示形式 安全派生的指针值
  • 可追踪指针对象是[...] 整数类型的对象,至少与reinterpret_cast
  • 一样大

该标准进一步指出实现可能放宽或可能严格执行安全派生指针。这意味着未指定使用或取消引用不安全派生的指针是否会调用未定义的行为(这很有趣!)

这一切意味着没有更多也不少于“不同的可能工作的东西,但唯一安全的事情就是如上所述”。

因此,如果您首先使用std::intptr_t(最喜欢的事情!)或者您知道您使用的任何整数类型的存储大小(例如, std::intptr_t)至少是long的大小,然后它是允许的并且定义良好(即“安全”)以转换为整数类型并返回。该标准保证了。

如果不是这种情况,则从指针到整数表示的转换可能(或至少可能)丢失一些信息,并且转换回不会给出有效指针。或者,可能意外,但这不能保证。

一个有趣的轶事是C ++标准根本没有直接定义std::intptr_t;它只是说“与C标准中的7.18相同”

另一方面,C标准声明“指定一个带有任何有效属性的有符号整数类型 指向void的指针可以转换为这种类型,然后转换回指向void的指针,结果将比较原始指针“。” 这意味着,如果没有上面相当复杂的定义(特别是第一个项目符号点的最后一位),除了std::intptr_t之外,不允许转换为/来自任何其他内容。

答案 3 :(得分:5)

一般来说,没有;指针可能大于int,在这种情况下,无法重建值。

如果已知整数类型足够大,那么你可以;根据标准(5.2.10 / 5):

  

转换为足够大小的整数的指针...并返回相同的指针类型将具有其原始值

但是,在C ++ 03中,没有标准的方法来判断哪些整数类型足够大。 C ++ 11和C99(以及实际上大多数 C ++ 03实现),以及Boost.Integer,为此目的定义intptr_tuintptr_t。或者您可以定义自己的类型并断言(最好在编译时)它足够大;或者,如果您没有特殊原因使其成为整数类型,请使用void*

答案 4 :(得分:3)

安全吗?不是真的。

在大多数情况下,它会起作用吗?是

当然,如果int太小而无法保存完整的指针值并截断,那么你将无法获得原始指针(希望你的编译器会警告你这种情况,GCC将指针转换为截断整数是硬错误)。如果您的图书馆支持longuintptr_t,则可能是更好的选择。

即使您的整数类型和指针类型大小相同,它也不一定会起作用,具体取决于您的应用程序运行时。特别是,如果你在程序中使用垃圾收集器,它可能很容易决定指针不再是未完成的,当你稍后将整数转换回指针并尝试取消引用它时,你会发现对象已经收获了。

答案 5 :(得分:2)

绝对不是。做一些假设假设int和指针的大小相同。在64位平台上几乎总是如此。如果它们不相同,则会发生精度损失,并且最终指针值将不正确。

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

答案 6 :(得分:2)

不,不是(总是)安全(因此通常不安全)。标准涵盖了

ISO C ++ 2003,5.2.10:

  
      
  1. 指针可以显式转换为任何整数类型大到足以容纳它。映射函数是实现定义的。
  2.   
  3. 可以将整数类型或枚举类型的值显式转换为指针。转换为足够大小的整数,如果在实现上存在)并返回相同指针类型的指针将具有其原始值;指针和整数之间的映射是实现定义的。
  4.   

(以上重点是我的。)

因此,如果您知道尺寸兼容,则转换 是安全的。

#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;
}

答案 7 :(得分:0)

使用“stdint.h”或“boost / stdint.h”中的uintptr_t。保证有足够的存储空间用于指针。

答案 8 :(得分:0)

不,不是。即使我们排除了体系结构问题,指针的大小和整数也有差异。 C ++中的指针可以有三种类型:near,far和huge。它们有不同的尺寸。如果我们谈论一个通常为16或32位的整数。因此将整数转换为指针和反之亦然是不安全的。必须非常小心,因为精确损失的可能性非常大。在大多数情况下,整数将缺少空间来存储指针,从而导致价值损失。

答案 9 :(得分:0)

如果您要进行任何系统便携式转换,您需要使用像Microsofts INT_PTR/UINT_PTR这样的东西,之后的安全依赖于目标平台以及您打算对INT_PTR做什么。通常对于大多数算术char *或uint_8 *在typesafe(ish)

时效果更好

答案 10 :(得分:0)

到一个int?并非总是如果你在64位机器上,那么int只有4个字节,但是指针长度为8个字节,因此当你从int转换回来时你会得到一个不同的指针。

但是有办法解决这个问题。您可以简单地使用8字节长的数据类型,无论您是否在32/64位系统上都可以使用,例如unsigned long long无符号,因为您不希望在32位系统上使用符号扩展。

重要的是要注意,在Linux unsigned long上将始终是指针大小 * 所以如果你的目标是Linux系统,你可以使用它。

*根据cppreference并自己测试,但不是在所有类似Linux和Linux的系统上进行测试

答案 11 :(得分:0)

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