什么时候 uintptr_t 优于 intptr_t?

When is uintptr_t preferred over intptr_t?

考虑到我需要将 "generic" 指针的值存储在结构中并且对指向的内存本身不感兴趣,我发现将其存储为 intptr_t 比一个 void*。问题是 uintptr_t 是否更适合,以及通常什么时候一个比另一个更受欢迎?

它主要是风格论点(优化编译器可能会生成相同或非常相似的代码)。然而,指针比较可能是一个棘手的问题。

请记住,在纯标准 C 中,指针比较仅对指向 相同 聚合数据的指针大致有意义。您可能不允许比较来自 malloc 的两个结果,例如保持一个排序的指针数组。

我会将它们保留为 void*,否则为 uintptr_t。带符号的 intptr_t 在分隔负数和正数方面存在不便,而且它们来自重要的应用程序指针,这可能不受欢迎。

请注意 void* 无法取消引用:作为 uintptr_t,您 必须 强制转换它以做一些有用的事情地址指向的数据;但是 void* 指针可以传递给例程,例如 memset

PS。我假设一个普通处理器(例如某些 x86、PowerPC、ARM 等)具有平面虚拟地址 space。您会发现异国情调的处理器——也许是一些 DSP——具有非常显着的差异(也许 intptr_t 并不总是有意义;请记住,在 1990 年代 Cray Y-MP supercomputers sizeof(long*) != sizeof(char*); at that time C99 并不存在,我不确定它的 <stdint.h> 在此类机器上可能有意义)

这听起来很奇怪,因为它需要转换。 C 中的 void * 具有巨大的优势,它可以转换 to/from 其他对象指针类型而无需强制转换,这非常干净。

如果你想对指针的位做一些你不能用带符号整数做的事情(比如将它们向右移动,例如).

如果您想以算术方式操作值(例如,对其进行加密),则使用无符号类型(其中算术回绕)比使用有符号类型(其中算术溢出给出未定义的行为)具有更大的灵活性。

您应该选择适合给定系统和程序的类型。大多数时候,指针是正地址值,在这种情况下 uintptr_t 是正确的类型。但是有些系统使用负地址作为表达内核的一种方式 space,如下所述:Can a pointer (address) ever be negative? 这就是为什么有两种不同类型的原因。


至于 (u)intptr_tvoid* 对于通用指针类型,前者在坚固的专业程序中更受欢迎。有许多 problems/bug 与指针类型相关的来源:

  • 各种不同的指针类型通常彼此不兼容并且不能别名。这是对象指针和函数指针的问题。
  • 您经常使用像 const 这样的类型限定符,这使得指针转换 to/from 类型有问题或定义不明确。
  • 转换 to/from void* 和其他指针类型隐式发生,使得与使用错误指针类型相关的错误很容易被忽视。这已在 C++ 中修复,但在 C 中仍然是一个危险。以旧的但经典的 "I forgot to include stdlib.h while using malloc in C90" 错误为例。
  • 对指针执行算术运算会带来许多陷阱,因为您只能安全地对指向已分配数组的指针执行算术运算。但是,正如任何使用嵌入式系统的人都知道的那样,除了指向数组之外,还有很多其他原因可以拥有内存地址。
  • 不能甚至对void*执行指针算术计算。这样做依赖于非标准编译器扩展。

也就是说,很多遗留代码都依赖于 void 指针,在受限的上下文中使用它们完全没问题。一些示例是依赖于通用回调函数的规范代码:bsearchqsort、pthreads 和类似的。

然而,我不建议在设计新的 C 程序时使用 void 指针 - 在我看来,它们最好被视为过去的危险特征。现在有更好更安全的通用 C 编程方法,例如 C11 _Generic、使用指定初始化器的技巧、将参数作为数组指针传递(到 VLA)、在编译时使用 static_assert 进行边界检查等。可以在我的回答中找到一些示例:.