C指针地址操作符
地址操作符&
会返回操作数的地址。我们可以用这个操作符来初始化pi
指针,如下所示:
num = 0;
pi = #
num
变量设置为0,而pi
设置为指向num
的地址:
可以在声明变量pi
的同时把它初始化为num
的地址,如下所示:
int num;
int *pi = #
有了以上声明,下面的语句在大部分编译器中都会报语法错误:
num = 0;
pi = num;
错误看起来可能是这样的:
error: invalid conversion from 'int' to 'int*'
pi
变量的类型是整数指针,而num
的类型是整数。这个错误消息是说整数不能转换为指向整数类型的指针。
把整数赋值给指针一般都会导致警告或错误。
指针和整数不一样。在大部分机器上,可能两者都是存储为相同字节数的数字,但它们不一样。不过,也可以把整数转换为指向整数的指针:
pi = (int *)num;
这样不会产生语法错误。不过运行起来后,程序可能会因为试图解引地址0处的值而非正常退出。在大部分操作系统中,在程序中使用地址0是不合法的。我们会在1.1.8节中详细讨论这个问题。
尽快初始化指针是一个好习惯,如下所示:
int num; int *pi; pi = #
打印C指针的值
我们实际使用的变量几乎不可能有100或104这样的地址。不过,变量的地址可以通过打印来确定,如下所示:
int num = 0;
int *pi = #
printf("Address of num: %d Value: %d\n",&num, num);
printf("Address of pi: %d Value: %d\n",&pi, pi);
运行后,会得到下面的输出。在这个例子中我们用了真实的地址,你的地址可能会不一样:
Address of num: 4520836 Value: 0
Address of pi: 4520824 Value: 4520836
printf
函数还有其他几种格式说明符在打印指针的值时比较有用
格式说明符 | 含义 |
---|---|
%x |
将值显示为十六进制数 |
%o |
将值显示为八进制数 |
%p |
将值显示为实现专用的格式,通常是十六进制数 |
这些说明符的用法如下:
printf("Address of pi: %d Value: %d\n",&pi, pi);
printf("Address of pi: %x Value: %x\n",&pi, pi);
printf("Address of pi: %o Value: %o\n",&pi, pi);
printf("Address of pi: %p Value: %p\n",&pi, pi);
这样就会显示pi
的地址和内容,如下所示。在这个例子中,pi
持有num
的地址:
Address of pi: 4520824 Value: 4520836
Address of pi: 44fb78 Value: 44fb84
Address of pi: 21175570 Value: 21175604
Address of pi: 0044FB78 Value: 0044FB84
%p
和%x
的不同之处在于:%p
一般会把数字显示为十六进制大写。如果没有特别说明,我们用%p
作为地址的说明符。
在不同的平台上用一致的方式显示指针的值比较困难。一种方法是把指针转换为void
指针,然后用%p
格式说明符来显示,如下:
printf("Value of pi: %p\n", (void*)pi);
void
指针会在1.1.8节的“void指针”中解释。为了保证示例简单,我们会用%p
说明符,而不把地址转换为void
指针。
虚拟内存和指针
让打印地址变得更为复杂的是,在虚拟操作系统上显示的指针地址一般不是真实的物理内存地址。虚拟操作系统允许程序分布在机器的物理地址空间上。应用程序分为页(或帧),这些页表示内存中的区域。应用程序的页被分配在不同的(可能是不相邻的)内存区域上,而且可能不是同时处于内存中。如果操作系统需要占用被某一页占据的内存,可以将这些内存交换到二级存储器中,待将来需要时再装载进内存中(内存地址一般都会与之前的不同)。这种能力为虚拟操作系统管理内存提供了相当大的灵活性。
每个程序都假定自己能够访问机器的整个物理内存空间,实际上却不是。程序使用的地址是虚拟地址。操作系统会在需要时把虚拟地址映射为物理内存地址。
这意味着页中的代码和数据在程序运行时可能位于不同的物理位置。应用程序的虚拟地址不会变,就是我们在查看指针内容时看到的地址。操作系统会帮我们将虚拟地址映射为真实地址。
操作系统处理一切事务,程序员无法控制也不需要关心。理解这些问题就能解释在虚拟操作系统中运行的程序所返回的地址。
指向函数的指针
指针可以声明为指向函数,声明的写法有点难记。下面的代码说明如何声明一个指向函数的指针。函数没有参数也没有返回值。指针的名字是foo:
void (*foo)();