C ++:将指针强制转换为int并返回到指针是否安全?
将指针强制转换为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; }
这是由标准覆盖?
没有。
例如,在x86-64上,一个指针是64位长,但是int
只有32位长。 将指针投射到int并再次返回使指针值的高32位丢失。
如果你想要一个和指针一样长的整数types,你可以在<cstdint>
使用intptr_t
types。 你可以安全地reinterpret_cast从一个intptr_t
指针返回。
是和不是。
只要整型的大小足以存储指针的[依赖于实现的]整数表示,语言规范就明确指出它是安全的(意味着最终你将得到原始的指针值)。
所以,一般情况下它不是“安全的”,因为在一般情况下int
可能会变得太小。 在你的具体情况下,虽然它可能是安全的,因为你的int
可能足够大,以存储你的指针。
通常情况下,当你需要这样做时,你应该使用intptr_t
/ uintptr_t
types,这是专门为此目的而引入的。 不幸的是, intptr_t
/ uintptr_t
不是当前C ++标准的一部分(它们是标准的C99types),但是许多实现提供了它们。 当然你可以自己定义这些types。
是,如果… (或“是,但是…”),否则。
该标准规定(3.7.4.3)如下:
- 一个指针值是一个安全派生的指针[…],如果它是一个定义良好的指针转换的结果,或者是一个安全派生指针值的
reinterpret_cast
[或]一个安全的整数表示reinterpret_cast
的结果指针值 - 整型值是一个安全派生指针[…]的整型表示,如果它的types至less与
std::intptr_t
一样大,并且[…]安全派生指针值reinterpret_cast
的结果[或]安全派生指针值的整数表示forms的有效转换结果[或]加法或按位操作的结果,其操作数之一是安全派生指针值的整数表示forms - 可跟踪指针对象是一个至less与
std::intptr_t
一样大的整型对象
该标准进一步指出,实现可能放宽或者可能严格执行安全派生的指针。 这意味着不确定是否使用或取消引用不安全的派生指针调用未定义的行为(这是一个有趣的事情!)
所有这一切都意味着没有更多,不亚于“不同的东西可能工作,但唯一安全的东西是如上所述”。
因此, 如果你首先使用std::intptr_t
(比较可取的事情!),或者你知道任何你使用的整数types(比如long
)的存储大小至less是std::intptr_t
,那么它是允许和明确的(即“安全”)转换为您的整数types,并返回。 标准保证。
如果情况并非如此,则从指针到整数表示的转换可能(或至less可能)会丢失一些信息,并且转换回不会产生有效的指针。 或者,这可能是偶然的,但这并不能保证。
一个有趣的轶事是,C ++标准根本不直接定义std::intptr_t
; 它只是说“与C标准中的7.18相同” 。
另一方面,C标准规定“指定一个带符号的整数types的属性,任何有效的void指针都可以被转换为这个types,然后转换回void指针,结果将与原指针“ 。
这意味着,如果没有上面相当复杂的定义(特别是第一个要点的最后一点),就不能将其转换成任何东西,而只能是void*
。
一般来说,没有; 指针可能比int
,在这种情况下,无法重build值。
如果一个整数types已知足够大,那么你可以; 根据标准(5.2.10 / 5):
一个转换为一个足够大小的整数的指针…回到相同的指针types将有其原始值
但是,在C ++ 03中,没有标准的方法来确定哪些整型足够大。 C ++ 11和C99(因此实际上大多数 C ++ 03实现)以及Boost.Integer,为此目的定义intptr_t
和uintptr_t
。 或者你可以定义你自己的types并断言(最好在编译时)它足够大; 或者,如果你没有什么特别的理由,它是一个整数types,使用void*
。
安全吗? 不是真的。
在大多数情况下,它会工作吗? 是
当然,如果一个int
太小而不能保存完整的指针值并截断,那么你不会得到你的原始指针(希望你的编译器会警告你这种情况,用GCC截断从指针到整数的转换是很难的错误)。 如果你的图书馆支持它,那么long
或者uintptr_t
可能是更好的select。
即使您的整数types和指针types的大小相同,也不一定取决于您的应用程序运行时。 特别是,如果您在程序中使用垃圾回收器,可能会轻易地决定指针不再是未完成的,并且当您稍后将整数转换回指针并尝试解除引用时,您会发现该对象已经收获了。
绝对不。 做一些假设一个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
不,它不是(总是)安全的(因此不安全)。 它被标准覆盖。
ISO C ++ 2003,5.2.10:
- 指针可以显式转换为任何足够大的整数types来保存它 。 映射函数是实现定义的。
- 整数types或枚举types的值可以显式转换为指针。 一个被转换为一个足够大小的整数的指针( 如果在这个实现上存在的话)并且回到相同的指针types将会有它的原始值。 指针和整数之间的映射在其他方面是实现定义的。
(上面的重点是我的。)
因此,如果您知道尺寸是兼容的,那么转换是安全的。
#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; }
使用“stdint.h”或“boost / stdint.h”中的uintptr_t。 这是保证有足够的存储指针。
不它不是。 即使我们排除了架构问题,指针和整数的大小也有差异。 一个指针可以有三种types的C ++:near,far和huge。 他们有不同的尺寸。 如果我们谈论一个正常的16位或32位的整数。 所以把整数转换成指针和副词是不安全的。 必须非常小心,因为有很大的精度损失的机会。 在大多数情况下,整数空间不足以存储指针,导致值的丢失。
如果你要做任何系统的可移植的铸造,你需要使用类似于微软的INT_PTR / UINT_PTR ,之后的安全依赖于目标平台以及你打算对INT_PTR做什么。 通常对于大多数算术字符*或uint_8 *更好的作品types安全(ish)
给一个int? 并不总是如果你是在一个64位的机器上,那么int只有4个字节,但是指针是8个字节长,因此当你从int转换回来时,你会得到一个不同的指针。
但是有办法解决这个问题。 您可以简单地使用一个8字节长的数据types,无论您是否位于32/64位系统上,都可以正常工作,比如unsigned long long
整型无符号整型,因为您不需要在32位系统上进行符号扩展。
需要注意的是,在Linux上, unsigned long
将始终是指针大小,所以如果你的目标是Linux系统,你可以使用它。
*根据cppreference ,也testing它自己,但不是所有的Linux和Linux系统
如果问题是你想对其进行正常的math运算,最安全的做法可能是把它转换成一个指向char的指针(或者更好的办法是, * uint8_t
),做你的math计算,然后把它转换回来。