什么时候整数< - >指针转换实际上是正确的?

时间:2011-08-22 11:31:16

标签: c++ c pointers casting

普通的民间传说说:

  • 类型系统存在是有原因的。整数和指针是不同的类型,在大多数情况下,它们之间的转换是一种弊端,可能表示设计错误,应该避免。

  • 即使执行了这样的强制转换,也不会对整数和指针的大小做出任何假设(将void*转换为int是使代码在x64上失败的最简单方法),而不是int,而应使用intptr_t中的uintptr_tstdint.h

知道何时实际有用来执行此类演员?

(注意:可移植性价格的代码稍微短一点并不算“实际有用”。)


我知道一个案例:

  • 一些无锁多处理器算法利用2 +字节对齐指针具有一定冗余的事实。然后,他们使用指针的最低位作为布尔标志。如果处理器具有适当的指令集,则可以消除对锁定机制的需要(如果指针和布尔标志是分开的,则需要这样做。)
    (注意:这种做法甚至可以通过java.util.concurrent.atomic.AtomicMarkableReference安全地在Java中完成)

还有什么?

15 个答案:

答案 0 :(得分:38)

当他们某种程度上需要成为hashsum的一部分时,我有时会将指针转换为整数。我还将它们转换为整数,以便在某些实现中对它们进行一些操作,在这些实现中保证指针总是剩下一个或两个备用位,在那里我可以在左/右指针中编码AVL或RB树信息而不是额外的会员。但这完全是具体的实现,我建议永远不要将其视为任何一种常见的解决方案。我也听说有时可以用这样的东西实现危险指针。

在某些情况下,我需要为每个对象提供一个唯一的ID,服务器作为我的请求ID。根据我需要保存一些内存的上下文,这是值得的,我使用我的对象的地址作为这样的id,并且通常必须将它转换为整数。

使用嵌入式系统时(例如在佳能相机中,请参见chdk),通常会有神奇的瑕疵,因此通常也会找到(void*)0xFFBC5235或类似的

修改

pthread_self()之后发现(在我的脑海中),它返回了一个pthread_t,它通常是一个无符号整数的typedef。在内部,虽然它是一个指向某个线程结构的指针,表示有问题的线程。一般来说,它可能在别处用于不透明的句柄。

答案 1 :(得分:15)

在检查一般类型的对齐时,它可能很有用,这样就可以使用断言捕获未对齐的内存而不仅仅是SIGBUS / SIGSEGV。

E.g:

#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上赌博,但它说明了这一点)

答案 2 :(得分:12)

使用一半空间存储双向链表

XOR Linked List将next和prev指针组合成相同大小的单个值。它通过将两个指针对齐来实现这一点,这需要将它们视为整数。

答案 3 :(得分:8)

一个例子是在Windows中,例如SendMessage()PostMessage()函数。它们采用HWnd(窗口句柄),消息(整数类型)和消息的两个参数WPARAMLPARAM。两种参数类型都是完整的,但有时您必须传递指针,具体取决于您发送的消息。然后,您必须指向LPARAMWPARAM

我通常会像瘟疫一样避免它。如果需要存储指针,请使用指针类型(如果可能)。

答案 4 :(得分:8)

在我看来,最有用的案例是实际上有可能使程序更高效的程序:许多标准和公共库接口采用单个void *参数,它们将传递回回调某种功能。假设您的回调不需要任何大量数据,只需要一个整数参数。

如果回调将在函数返回之前发生,您可以简单地传递本地(自动)int变量的地址,一切都很好。但是这种情况的最佳现实示例是pthread_create,其中“回调”并行运行,并且您无法保证在pthread_create返回之前它能够通过指针读取参数。在这种情况下,您有3个选项:

  1. malloc一个int并阅读新帖子并free
  2. 将指针传递给包含int的调用者本地结构和同步对象(例如信号量或屏障),并在调用pthread_create后让调用者等待它。
  3. int投射到void *并按值传递。
  4. 选项3比其他任何选项都更有效率,这两个选项都涉及额外的同步步骤(对于选项1,同步在malloc / free,并且几乎肯定会涉及由于分配和释放线程不一样,所以需要付出一些代价。)

答案 5 :(得分:6)

在嵌入式系统中访问内存映射硬件设备非常常见,其中寄存器位于内存映射中的固定地址。我经常在C与C ++中对硬件进行不同的建模(使用C ++可以利用类和模板),但一般的想法可以用于两者。

一个简单的例子:假设你有一个硬件定时器外设,它有2个32位寄存器:

  • 自由运行的“滴答计数”寄存器,以固定速率递减(例如每微秒)

  • 一个控制寄存器,它允许你启动定时器,停止定时器,当我们将计数减少到零时启用定时器中断等等。

(请注意,实时定时器外设通常要复杂得多)。

这些寄存器中的每一个都是32位值,定时器外设的“基地址”是0xFFFF.0000。您可以按如下方式对硬件进行建模:

// 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.
// e.g. read the 32-bit value @ 0xFFFF.0000
uint32_t CurrentTicks = Timer1->TimerCount;

// Stop / reset the timer.
// e.g. write the value 0 to the 32-bit location @ 0xFFFF.0004
Timer1->TimerControl = 0;

这种方法有100种变体,其优缺点可以永远辩论,但这里的重点仅仅是说明将整数转换为指针的常见用法。请注意,此代码不可移植,与特定设备绑定,假设内存区域不受限制等。

答案 6 :(得分:3)

除非您完全了解编译器+平台组合的行为并希望利用它,否则执行此类强制转换永远不会有用(您的问题场景就是这样一个示例)。

我之所以说它永远不会有用的原因是因为一般来说,你无法控制编译器,也不知道它可能选择做什么优化。换句话说,您无法精确控制它将生成的机器代码。所以一般来说,你不能安全地实现这种技巧。

答案 7 :(得分:2)

我将pointer转换为integer的唯一时间是我想要存储指针,但我唯一可用的存储是整数。

答案 8 :(得分:2)

什么时候将指针存储在整数中是正确的?当你将它视为现实时它是正确的:使用平台或编译器特定的行为。

问题是,只有在整个应用程序中都有特定于平台/编译器的代码,并且必须将代码移植到另一个平台,因为您已经做出了不再适用的假设。通过隔离该代码并将其隐藏在不对底层平台做出任何假设的接口之后,您可以消除该问题。

因此,只要您记录实现,使用句柄或不依赖于它如何在幕后工作的东西将其分离在独立于平台的接口后面,然后仅在平台/编译器上有条件地编译代码。经过测试和运作,那么你没有理由不使用你遇到的任何巫毒魔法。如果需要,您甚至可以包含大量汇编语言,专有API调用和内核系统调用。

也就是说,如果你的“可移植”接口使用整数句柄,那么整数与某个平台的实现上的指针大小相同,并且该实现在内部使用指针,为什么不简单地将指针用作整数句柄呢?在这种情况下,对整数的简单转换是有意义的,因为你删除了某种句柄/指针查找表的必要性。

答案 9 :(得分:1)

您可能需要访问固定已知地址的内存,然后您的地址是整数,您需要将其分配给指针。这在嵌入式系统中有些常见。相反,您可能需要打印内存地址,因此需要将其转换为整数。

哦,不要忘记你需要分配和比较指向NULL的指针,这通常是0L的指针转换

答案 10 :(得分:1)

我在网络范围的ID对象中有一个用途。这样的ID将组合机器的标识(例如IP地址),进程ID和对象的地址。要通过套接字发送,必须将这种ID的指针部分放入足够宽的整数中,以便它能够来回传输。指针部分仅在上下文中被解释为指针(=强制转换为指针),在其他机器或其他进程中,它仅用于区分不同的对象。

需要工作的东西是存在uintptr_tuint64_t作为固定宽度整数类型。 (只适用于最多64个地址的机器:)

答案 11 :(得分:1)

在x64下,on可以使用指针的高位进行标记(因为只有47位用于实际指针)。这对于运行时代码生成(LuaJIT使用这种技术,这是一种古老的技术,根据评论)非常有用,要做这个标记和标记检查,你需要一个强制转换或union,这基本上相同的事情。

指向整数的指针在使用分箱的内存管理系统中也非常有用,即:一个人能够通过一些数学方法轻松找到地址的bin / page,一个来自无锁分配器的例子我写了一会儿:

inline Page* GetPage(void* pMemory)
{
    return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift];
}

答案 12 :(得分:0)

当我试图逐个字节地通过数组时,我已经使用过这样的系统。通常,指针一次会走多个字节,这会导致很难诊断的问题。

例如,int指针:

int* my_pointer;

移动my_pointer++将导致前进4个字节(在标准的32位系统中)。但是,移动((int)my_pointer)++会使其前进一个字节。

除了将指针转换为(char *)之外,它确实是实现它的唯一方法。 ((char*)my_pointer)++

不可否认,(char *)是我常用的方法,因为它更有意义。

答案 13 :(得分:0)

指针值也可以成为播种随机数生成器的有用熵源:

int* p = new int();
seed(intptr_t(p) ^ *p);
delete p;

boost UUID库使用了这个技巧,还有其他一些。

答案 14 :(得分:0)

使用指向对象的指针作为无类型句柄有一个古老而好的传统。例如,有些人使用它来实现具有扁平C风格API的两个C ++单元之间的交互。在这种情况下,句柄类型被定义为整数类型之一,并且任何方法必须将指针转换为整数,然后才能将其转换为另一种方法,该方法期望抽象无类型句柄作为其参数之一。此外,有时没有其他方法可以打破循环依赖。