什么时候是一个整数< – >指针转换实际上是正确的?
民间传说说:
-
types系统存在的原因。 整数和指针是不同的types,在大多数情况下铸造它们是一种弊端,可能表明devise错误,应该避免。
-
即使在执行这样的转换时,也不会对整数和指针的大小进行假设(将
void*
为int
是使代码在x64上失败的最简单方法),而不是int
应该使用intptr_t
或uintptr_t
stdint.h
。
知道什么时候执行这样的演员实际上是有用的 ?
(注意:对可移植性的代价稍微短一点的代码并不算“实际上有用”)。
我知道的一个例子是:
- 一些无锁多处理器algorithm利用了2 + -byte-alligned指针有一些冗余的事实。 然后,他们使用指针的最低位作为布尔标志,例如。 使用具有适当指令集的处理器,这可以消除对locking机制的需要(如果指针和布尔标志是分离的,则这是必要的)。
(注意:这种做法甚至可以通过java.util.concurrent.atomic.AtomicMarkableReference在Java中安全地执行)
更多的东西?
我有时候把它们指向整数,当它们以某种方式需要成为一个hashsum的一部分的时候。 我也将它们转换为整数,以便在某些实现中对其进行一些处理,以确保指针总是留有一个或两个备用位,我可以在左/右指针中编码AVL或RB树信息,而不需要额外的会员。 但是,这些都是特定于实现的,所以我build议不要将其视为任何一种常见的解决scheme。 我也听说有时危险指针可以用这样的东西来实现。
在某些情况下,我需要每个对象的唯一ID,例如我的请求ID传递给服务器。 当我需要保存一些内存时,根据上下文,这是值得的,我使用我的对象的地址作为这样一个ID,通常必须将其转换为一个整数。
在使用embedded式系统时(例如canon相机,请参阅chdk),经常会出现一些幻想,所以(void*)0xFFBC5235
或类似
编辑:
只是偶然(在我的脑海里)通过pthread_self()
返回一个pthread_t,它通常是一个无符号整数的typedef。 在内部,虽然它是一个指向某个线程结构的指针,表示正在讨论的线程。 一般来说,它可能用于其他地方的不透明手柄。
在检查一般types的alignment方式时可能会很有用,以避免错位的内存被一个断言捕获,而不仅仅是SIGBUS / SIGSEGV。
例如:
#include <xmmintrin.h> #include <assert.h> #include <stdint.h> int main() { void *ptr = malloc(sizeof(__m128)); assert(!((intptr_t)ptr) % __alignof__(__m128)); return 0; }
(显然,如果这是真实的代码,我实际上不会在malloc
赌钱,但它说明了这一点)
用一半的空间存储一个双向链表
XOR链接列表将下一个和前一个指针合并为一个相同大小的单个值。 这是通过将两个指针异或来完成的,它需要把它们看作整数。
我脑海里最有用的一个例子就是实际上有可能使程序更高效的一个例子:一些标准的和通用的库接口采用了一个void *
参数,他们将回传给某种callback函数。 假设你的callback不需要任何大量的数据,只需要一个整数参数。
如果callback在函数返回之前发生,那么可以简单地传递一个本地(自动) int
variables的地址,并且一切正常。 但是,对于这种情况,最好的现实世界的例子是pthread_create
,其中“callback”并行运行,并且不能保证能够在pthread_create
返回之前通过指针读取参数。 在这种情况下,你有3个select:
-
malloc
一个int
并有新的线程读取和free
它。 - 传递一个指向包含
int
和一个同步对象(例如信号量或障碍)的调用者本地结构的指针,并在调用pthread_create
之后让调用者等待它。 - 将
int
为void *
并通过值传递。
选项3比其他任何选项都要高效得多,这两个选项都涉及一个额外的同步步骤(对于选项1,同步处于malloc
/ free
,因为分配和释放线程不是相同)。
一个例子是在Windows中,例如SendMessage()
和PostMessage()
函数。 他们采取一个HWnd
(一个窗口的句柄),一个消息(一个整型)和两个消息参数,一个WPARAM
和一个LPARAM
。 两个参数types都是不可或缺的,但有时您必须传递指针,具体取决于您发送的消息。 那么你将不得不把一个指针指向一个LPARAM
或WPARAM
。
我通常会避免像瘟疫一样 。 如果你需要存储一个指针,使用指针types,如果可能的话。
在embedded式系统中,访问存储器映射中存储器映射的固定地址的存储器映射硬件设备是非常普遍的。 我经常在C和C ++中用不同的硬件build模(用C ++你可以利用类和模板),但是总的想法可以用于两者。
一个简单的例子:假设你有一个硬件定时器外设,它有2个32位寄存器:
-
自由运行的“滴答计数”寄存器,以固定速率递减(例如每微秒)
-
一个控制寄存器,允许你启动定时器,停止定时器,当我们将计数递减到零时启动一个定时器中断。
(请注意,实时定时器外设通常要复杂得多)。
每个寄存器都是32位值,定时器外设的“基地址”是0xFFFF.0000。 你可以build模硬件如下:
// Treat these HW regs as volatile typedef uint32_t volatile hw_reg; // C friendly, hence the typedef typedef struct { hw_reg TimerCount; hw_reg TimerControl; } TIMER; // Cast the integer 0xFFFF0000 as being the base address of a timer peripheral. #define Timer1 ((TIMER *)0xFFFF0000) // Read the current timer tick value. // eg read the 32-bit value @ 0xFFFF.0000 uint32_t CurrentTicks = Timer1->TimerCount; // Stop / reset the timer. // eg write the value 0 to the 32-bit location @ 0xFFFF.0004 Timer1->TimerControl = 0;
这种方法有100个变种,其优点和缺点可以永远辩论,但这里的重点仅仅是说明将一个整数转换为指针的常见用法。 请注意,这个代码是不可移植的,绑定到一个特定的设备,假设内存区域不禁止,等等
除非您完全了解编译器+平台组合的行为,并且希望利用它(您的问题场景就是这样的一个例子),否则执行这样的强制types是毫无用处的。
我说这是没有用的原因是因为一般来说,你没有对编译器的控制,也没有完全知道它可以select做什么优化。 换句话说,您无法精确地控制它将生成的机器码。 所以一般来说,你不能安全地实现这种技巧。
唯一的时间我投给一个integer
的pointer
是当我想存储一个指针,但唯一的存储我有可用是一个整数。
什么时候存储指针是正确的? 当你把它当作它时是正确的:使用平台或编译器特定的行为。
只有在整个应用程序中散布平台/编译器特定的代码,并且必须将代码移植到另一个平台,因为您已经做出了不再成立的假设。 通过隔离该代码并将其隐藏在不会假设底层平台的界面之后,可以消除该问题。
所以,只要你logging实现,把它分离到一个独立于平台的接口上,使用句柄或者不依赖于它在幕后工作的东西,然后使代码仅在已经被testing的平台/编译器上有条件地编译,那么你没有理由不使用任何你所遇到的巫术魔法。 如果需要,甚至可以包含大量的汇编语言,专有API调用和内核系统调用。
也就是说,如果你的“便携式”接口使用整型句柄,那么整数和指定的平台实现的指针大小相同,并且该实现在内部使用指针,为什么不简单地使用指针作为整型句柄呢? 在这种情况下,简单的转换为整数是有意义的,因为你不需要某种types的句柄/指针查找表。
你可能需要访问一个固定的已知地址的内存,然后你的地址是一个整数,你需要把它分配给一个指针。 这在embedded式系统中很常见。 相反,您可能需要打印一个内存地址,因此需要将其转换为整数。
哦,不要忘了你需要分配和比较指向NULL的指针,这通常是0L的指针
我有一个用于这样的事情在networking广泛的ID的对象。 这样的ID将结合机器的标识(例如IP地址),进程ID和对象的地址。 要通过套接字发送,这样一个ID的指针部分必须放在一个足够宽的整数中,以便它能够来回传送。 指针部分只被解释为一个指针(=返回到一个指针)的上下文,这是有道理的(相同的机器,相同的进程),在其他机器或其他进程中,它只是用来区分不同的对象。
需要做的工作是存在uintptr_t
和uint64_t
作为固定宽度整数types。 (那么只能在最多有64个地址的机器上运行:)
在64位以下,可以使用指针的高位进行标记(因为实际指针只使用47位)。 对于运行时代码生成(LuaJIT使用这种技术,这是一种古老的技术,根据注释),这是非常好的,做这个标签和标签检查你需要一个演员或union
,基本上相同事情。
指向整数的指针也可以在使用分箱的内存pipe理系统中非常有用,例如:通过一些math方法,可以很容易地find一个地址的bin / page,我写了一个无锁分配器的例子背部:
inline Page* GetPage(void* pMemory) { return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift]; }
当我试图通过一个数组逐字节地走路时,我已经使用了这样的系统。 通常,指针会一次走多个字节,这会导致很难诊断的问题。
例如,int指针:
int* my_pointer;
移动my_pointer++
将导致前进4个字节(在标准的32位系统中)。 但是,移动((int)my_pointer)++
会使其前进一个字节。
除了将指针转换为(char *)以外,这是真正实现它的唯一方法。 ( (char*)my_pointer)++
无可否认,(char *)是我常用的方法,因为它更有意义。
指针值也可以作为播种随机数生成器的一个有用的熵源:
int* p = new int(); seed(intptr_t(p) ^ *p); delete p;
boost UUID库使用这个技巧,以及其他一些技巧。
将指针指向一个对象作为一个无types的句柄有一个古老的传统。 例如,有些人使用它来实现两个C ++单元之间使用平面C风格API的交互。 在这种情况下,句柄types被定义为整数types之一,任何方法都必须将指针转换为整数,然后才能将其转换为另一个需要抽象无types句柄作为其参数之一的方法。 另外,有时候没有其他办法可以打破循环依赖。