C 中的 uintptr_t 类型
1、uintptr_t
uintptr_t
在 C99 中的 ,作为一个可选类型。
任意合法的 void * 指针可转换为该类型值,再将该值转换回 void * 指针后,所得结果与初始指针值相等(在比较操作中判等)。
再大白话一点:主要用途是让我们可以将指针安全地转换为整数,再从整数恢复为指针,而不会丢失信息
- intptr_t:具备上述属性的带符号整型
- uintptr_t:具备上述属性的无符号整型
以下这些定义是按照标准 C 的最小要求实现的(例如 C99 要求 long 至少为 32 位)
位数 | char | short | int | long | 指针 |
---|---|---|---|---|---|
16 位系统 | 1 字节 | 2个字节 | 2个字节 | 4个字节 | 2个字节 |
32 位系统 | 1 字节 | 2个字节 | 4个字节 | 4个字节 | 4个字节 |
64 位系统 | 1 字节 | 2个字节 | 4个字节 | 8个字节 | 8个字节 |
指针在 32 位平台和 64 位平台下均与 long 类型的长度一致,然而在 16 位机器上,long 为 4 个字节,而指针为 2 个字节。
因此,就可以发现 intptr_t
和 uintptr_t
定义的巧妙之处:可以保证 intptr_t
和 uintptr_t
的长度与机器的指针长度一致,因此在进行整数与 指针的相互转换时可以用 intptr_t
进行过渡。uintptr_t
增强了程序的兼容性。
1.1 案例
我们实际举几个例子,看一下 uintptr_t
关键字的实际使用场景。
场景 1
pages
作为指针,需要强转成 _u64。借助 uintptr_t
过渡,先转成与指针等宽的无符号整数,语义明确且安全
如果不加 uintptr_t
过渡,可能被编译器视作不安全,并发出警告。
struct fastrpc_phy_page {u64 addr; /* physical address */u64 size; /* size of contiguous region */
};struct fastrpc_invoke_args {__u64 ptr;__u64 length;__s32 fd;__u32 attr;
};static int fastrpc_init_create_static_process(struct fastrpc_user *fl,char __user *argp)
{struct fastrpc_invoke_args *args;struct fastrpc_phy_page pages[1];......args[2].ptr = (u64)(uintptr_t) pages;
}
场景 2
smem_start
作为 unsigned long
型,先使用 uintptr_t
转成与指针等宽的无符号整数,语义明确且安全,再进行指针赋值。
如果不加 uintptr_t
过渡,可能被编译器视作不安全,并发出警告。
struct fb_fix_screeninfo {char id[16]; /* identification string eg "TT Builtin" */unsigned long smem_start; /* Start of frame buffer mem */......
}struct fb_info {......union {char __iomem *screen_base; /* Virtual address */char *screen_buffer;};......
}static int hitfb_probe(struct platform_device *dev)
{struct fb_info *info;......info->screen_base = (char __iomem *)(uintptr_t)hitfb_fix.smem_start;......
}
1.2 总结
总结一句话,intptr_t
、uintptr_r
变量类型用于跨平台的情况下,不同机器字长(16位、32位、64位)整数与指针相互转移的的通用性。
通常情况下,编译器内部都会提供 intptr_t
等定义。如果编译器没有提供,则标准库会根据架构用 typedef 自己定义。例如 glibc 标准库中的 /stdlib/stdint.h 文件中:
/* Types for `void *' pointers. */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned long int uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned int uintptr_t;
#endif
1.3 拓展
看了上面的讲解,不知道你会不会有个问题,为什么指针和整数之间的转换需要 uintptr_t
关键字?而整数和整数之间的转换就不需要?
这里我们就要了解指针在 C99 标准中是如何定义的《ISO/IEC 9899:1999 (E)》。
6.3.2.3 Pointers 章节
- A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
- 指向 void 的指针可以与指向任意不完全类型或对象类型的指针相互转换
- 指向任意不完全类型或对象类型的指针可以转换为指向 void 的指针,再转换回来,其结果应当与原始指针进行比较时相等
- Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type
- 任何指针类型均可转换为整数类型
- 除非前文另有规定,此类转换的结果为实现定义(implementation-defined)
- 除非标准前面有明确规定的特例,否则:把一个指针类型转换为一个整数类型时,转换后的结果值是由具体实现(如编译器 + 平台)自行定义的,并不由 C 语言标准统一规定
- 若该结果无法被目标整数类型所表示,则行为为未定义(undefined behavior)
- 如果你试图把一个指针值(例如 void *,通常是 64 位地址)转换成一个目标整数类型(比如 unsigned int,是 32 位),而这个整数类型无法容纳指针的原始值,那么行为是未定义的(undefined behavior)
- 该结果不必落在任何整数类型的值域范围之内
- 当你把一个指针转换成整数时,转换的结果可以是某个整数类型所不能表示的值(逻辑上的),因为这种转换是实现定义的,标准并不强制这个结果落在一个合法整数值的范围内
6.3.1.3 Signed and unsigned integers 章节
- When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
- Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
- Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised
这里就不再具体翻译了,总结一句话就是:所有整数之间的转换是标准定义清晰的,包括类型提升、截断、符号位处理等。
所以回到我们的场景 1。因为 pages 指针强转成 u64 是实现定义,即由编译器和平台决定。为了确保兼容性、所有平台都能正常运行,所以要先转换成 uintptr_t
一个整数,一个可以足够承载一个指针值,然后再去强转 u64,在语义和行为上具有明确性和安全性。
虽然现代几乎所有编译器都能够正确解决 指针强转整数 的操作,但是为了兼容性、规范性,我们还是需要使用 uintptr_t,否则部分编译器会在编译时蹦出警告
static int fastrpc_init_create_static_process(struct fastrpc_user *fl,char __user *argp)
{struct fastrpc_invoke_args *args;struct fastrpc_phy_page pages[1];......args[2].ptr = (u64)(uintptr_t) pages;
}