使用指针时经常用到以下四种预定义类型。
size_t
用于安全地表示长度。ptrdiff_t
用于处理指针算术运算。intptr_t
和uintptr_t
用于存储指针地址。
下面将展示每种类型的用法,ptrdiff_t
除外,我们会在1.3.1节的“两个指针相减”中讨论它。
理解size_t
size_t
类型表示C中任何对象所能达到的最大长度。它是无符号整数,因为负数在这里没有意义。它的目的是提供一种可移植的方法来声明与系统中可寻址的内存区域一致的长度。size_t
用做sizeof
操作符的返回值类型,同时也是很多函数的参数类型,包括malloc
和strlen
。
在声明诸如字符数或者数组索引这样的长度变量时用
size_t
是好的做法。它经常用于循环计数器、数组索引,有时候还用在指针算术运算上。
size_t
的声明是实现相关的。它出现在一个或多个标准头文件中,比如stdio.h和stblib.h,典型的定义如下:
#ifndef __SIZE_T
#define __SIZE_T
typedef unsigned int size_t;
#endif
define
指令确保它只被定义一次。实际的长度取决于实现。通常在32位系统上它的长度是32位,而在64位系统上则是64位。一般来说,size_t
可能的最大值是SIZE_MAX
。
通常
size_t
可以用来存放指针,但是假定size_t
和指针一样长不是个好主意。稍后的“使用sizeof
操作符和指针”会讲到,intptr_t
是更好的选择。
打印size_t
类型的值时要小心。这是无符号值,如果选错格式说明符,可能会得到不可靠的结果。推荐的格式说明符是%zu
。不过,某些情况下不能用这个说明符,作为替代,可以考虑%u
或%lu
。
下面这个例子将一个变量定义为size_t
,然后用两种不同的格式说明符来打印:
size_t sizet = -5;
printf("%d\n",sizet);
printf("%zu\n",sizet);
因为size_t
本来是用于表示正整数的,如果用来表示负数就会出问题。如果为其赋一个负数,然后用%d
和%zu
格式说明符打印,就得到如下结果:
-5
4294967291
%d
把size_t
当做有符号整数,它打印出-5因为变量中存放的就是-5。%zu
把size_t
当做无符号整数。当-5被解析为有符号数时,高位置为1,表示这个数是负数。当它被解析为无符号数时,高位的1被当做2的乘幂。所以在用%zu
格式说明符时才会看到那个大整数。
正数会正常显示,如下所示:
sizet = 5;
printf("%d\n",sizet); // 显示5
printf("%zu\n",sizet); // 显示5
因为size_t
是无符号的,一定要给这种类型的变量赋正数。
对指针使用sizeof
操作符
sizeof
操作符可以用来判断指针长度。下面的代码显示char
指针的长度:
printf("Size of *char: %d\n",sizeof(char*));
输出如下:
Size of *char: 4
注意 当需要用指针长度时,一定要用
sizeof
操作符。
函数指针的长度是可变的。通常,对于给定的操作系统和编译器组合,它是固定的。很多编译器支持创建32位和64位应用程序,所以对于同一个程序来说,不同的编译选项可能会导致其使用不同的指针长度。
在Harvard架构上,代码和数据存储在不同的物理内存中。比如Intel的MCS-51(8051)微处理器就是Harvard架构。尽管Intel不再生产这种芯片,但现在还是有很多二进制兼容的衍生品在使用。Small Device C Complier(SDCC)就支持这类处理器(参见http://sdcc.sourceforge.net/doc/sdccman.pdf)。这种机器的指针长度可能介于1到4字节之间,因此指针长度应该在需要时再确定,原因是在这种环境中它并不固定。
使用intptr_t
和uintptr_t
intptr_t
和uintptr_t
类型用来存放指针地址。它们提供了一种可移植且安全的方法声明指针,而且和系统中使用的指针长度相同,对于把指针转化成整数形式来说很有用。
uintptr_t
是intptr_t
的无符号版本。对于大部分操作,用intptr_t
比较好。uintptr_t
不像intptr_t
那样灵活。下面的例子说明如何使用intptr_t
:
int num;
intptr_t *pi = #
如果像下面那样试图把整数地址赋给uintptr_t
类型的指针,我们会得到一个语法错误:
uintptr_t *pu = #
错误看起来是这样的:
error: invalid conversion from 'int*' to
'uintptr_t* {aka unsigned int*}' [-fpermissive]
不过,用强制类型转换来赋值是可以的:
intptr_t *pi = #
uintptr_t *pu = (uintptr_t*)#
如果不转换类型,不能将uintptr_t
用于其他类型:
char c;
uintptr_t *pc = (uintptr_t*)&c;
当可移植性和安全性变得重要时,就应该使用这些类型。不过,为简单起见,我们的例子中不会使用。
避免把指针转换成整数。如果指针是64位,整数只有4字节时就会丢失信息。
早期的Intel处理器采用16位的分段架构,近指针和远指针也是相对的。今天的虚拟内存架构上就不是这样了。远指针和近指针是C标准的扩展,用来支持早期的Intel处理器的分段架构。近指针一次只能寻址64 KB的内存。远指针最多可以寻址1 MB内存,但是比近指针慢。巨指针是规范化过的远指针,使用尽可能高的段。