什么时候uintptr_t比intptr_t更受欢迎?
鉴于我需要在结构中存储“generics”指针的值并且对指向的内存本身没有兴趣,我发现将它存储为intptr_t
不是void*
语义上更正确。 问题是uintptr_t
是否更适合,并且当一个人比另一个更优先时?
它主要是一种风格论证(优化编译器可能生成相同或非常相似的代码)。 但是,指针比较可能是一个棘手的问题。
请记住,在纯粹的标准C指针比较中,仅对指向相同聚合数据的指针大致有意义。 您可能不允许比较来自malloc
两个结果,例如,保留已排序的指针数组。
我会把它们视为void*
,或者作为uintptr_t
。 签名的intptr_t
有分离负数和正数的不便,以及它们来自重要的应用程序指针,这可能不受欢迎。
请注意, void*
不能被取消引用:作为uintptr_t
,您必须将其uintptr_t
为对地址指向的数据执行有用的操作; 但是void*
指针可以传递给像memset
这样的例程
PS。 我假设一个普通的处理器(例如某些x86,PowerPC,ARM,……)具有平坦的虚拟地址空间。 您可以找到奇特的处理器 – 可能是某些DSP – 具有非常显着的差异(并且可能在其上intptr_t
并不总是有意义的;请记住在20世纪90年代Cray Y-MP超级计算机sizeof(long*) != sizeof(char*)
;在那时间C99不存在,我不确定它的
在这样的机器上是否有意义)
这听起来很奇怪,因为它需要演员阵容。 C中的void *
具有巨大的优势,它可以在没有强制转换的情况下转换为其他对象指针类型,也可以从其他对象指针类型进行转换。
也就是说uintptr_t
可能有意义,如果你想对指针的某些部分做一些你不能用有符号整数明智地做的事情(例如将它们移到右边)。
如果你想算术地操纵值(例如,加密它),你可以更灵活地使用无符号类型(算术环绕)而不是有符号类型(算术溢出给出未定义的行为)。
您应该选择适合给定系统和程序的类型。 大多数情况下,指针是正地址值,在这种情况下, uintptr_t
是正确的类型。 但是有些系统使用负地址作为表达内核空间的方式,如下所述: 指针(地址)是否可以是负数? 这就是为什么有两种不同类型的原因。
对于通用指针类型的(u)intptr_t
vs void*
,前者在粗糙的专业程序中是首选。 与指针类型相关的问题/错误源有很多:
- 各种不同的指针类型通常彼此不兼容,不能使用别名。 这是对象指针和函数指针的问题。
- 您经常使用类型限定符(如
const
,这使得指向该类型的指针转换有问题或定义不当。 - 来自
void*
和其他指针类型的转换是隐式发生的,这使得与使用错误指针类型相关的错误很容易被忽视。 这是在C ++中修复的,但在C中仍然存在危险。例如旧的但经典的“我忘了在C90中使用malloc时包含stdlib.h”错误。 - 对指针执行算术会带来许多陷阱,因为您只能安全地对指向已分配数组的指针进行算术运算。 但是,正如任何使用嵌入式系统的人都知道的那样,人们通常可以拥有一个内存地址,而不是指向一个数组。
- 您甚至无法对
void*
执行指针算术计算。 这样做依赖于非标准的编译器扩展。
话虽这么说,很多遗留代码依赖于void
指针,在受限制的上下文中使用它们是完全正确的。 一些例子是依赖generics回调函数的规范代码: bsearch
, qsort
,pthreads等。
但是我不建议在设计新的C程序时使用void
指针 – 在我看来,它们被认为是过去的一个危险特征。 现在存在更好更安全的通用C编程方法,例如C11 _Generic
,使用指定初始化器的技巧,将参数作为数组指针传递给VLA,使用static_assert
等在编译时进行边界检查等。一些例子可以在我的答案中找到这里: 如何创建类型安全枚举? 。