今天一个同事问我一个关于指针的问题,从讨论中,我可以感觉到他对指针的认识还不是很清楚——当然,这不是说我自己认识就清楚,相对清楚而已。指针,大概是C语言中最难理解的概念之一了,但是作为一个工作几年的程序员来说,还是应该对指针的理解要比较清楚。所以我今天就想好好的整理一下自己对指针的理解,即分享了知识和经验,也算自己的一个总结吧。
首先,指针这个东西,无疑是C语言中的一个基本概念。那么下面请看C99中对于指针的定义:
—A pointer type may be derived from a function type, an object type, or an incomplete
type, called the referenced type . A pointer type describes an object whose value
provides a reference to an entity of the referenced type. A pointer type derived from
the referenced type T is sometimes called ‘‘pointer to T ’’. The construction of a
pointer type from a referenced type is called ‘‘pointer type derivation’’.
这个定义不太好翻译——估计是因为我的英语蹩脚,但是其中有几点需要注意的是:
1. 指针的类型是derived from其它类型,也就是说指针的类型是由它指向的类型决定的;
2. 指针是一种reference类型,即引用类型;
上面是标准的定义,那么对于我来说,对指针的理解就是指针即变量。这句话听上去就是一句废话,不是变量还能是什么。但是也许有的朋友真的不能真正理解这句话。我在面试的时候,经常给应聘者出下面这道题目:
- #include <stdlib.h>
- #include <stdio.h>
-
- int main(void)
- {
- short *p = 0;
- int **pp = 0;
-
- ++p;
- ++pp;
- printf("%d %d\n", p, pp);
-
- return 0;
- }
如果对指针认识比较清楚的人,应该可以迅速说出答案。但是更多的候选人都是苦苦思索,想到程序在内存中的分布,作为elf可执行文件格式的起始地址为0x0804800,然后压栈去计算p和pp的地址,连物理内存虚拟内存的概念都扔了出来。等等。。。
其实这道题很简单,就是“指针即变量”的理解。指针,作为引用类型不假,其实现引用的途径就是将指向的object的地址存在指针本身的地址的内存空间。有些拗口吗?一步一步的说,指针本身也是一个变量,作为变量,它自然需要占用一块内存空间,那么自然拥有一个地址。当指针指向某个object时,就是将该object的地址存在指针自身地址的那块内存中。
下面对上面的题目做出解释,就会清楚很多。
short *p = 0;p是一个short *指针变量,将其赋值为0,其实与short a = 0;没有什么区别,都是将0赋给一个变量,即将0存入该变量所占用的内存地址中。那么这时,p所在的内存处存的是0——注意,这里是p所在的内存,而非p所指向的内存。
int **pp = 0,不要被这里的指针的指针类型所迷惑,其实与上面一样,都是将0赋给变量。那么这时,pp所在的内存处存的是0。
++p;
++pp;
这里考察的是指针的算术运算。指针的加减运算,需要考虑其类型,这个同样是标准定义。为什么呢,很简单。想想数组int array[3];array本身可看作指针,array+0是第一个元素的地址,array+1是第二个元素地址,所以指针的加减运算单位是sizeof(type),type为指针所指向的类型。那么此题中,p指向的类型为short,那么sizeof(short)为2,p之前的值为0,++p,这是p的值自然为2。p的值,即指针的值,其实就是指针所在内存所存的数值。而++pp,sizeof(int*)为4——int *为指针类型,32位机为4。那么++pp后,pp的值就是4。
printf("%d %d\n", p, pp);这里%d为打印整数的十进制的值,而p,pp为指针类型,所以这里有一个类型转换,即(int)p, (int)pp。即将p和pp的值转为整数类型,而其值为2和4,转换为整数自然仍然是2和4。
所以这道题的最后答案为2 4。
下面看一下反汇编的结果:
- Dump of assembler code for function main:
- 5 {
- 0x080483c4 <+0>: push %ebp
- 0x080483c5 <+1>: mov %esp,%ebp
- 0x080483c7 <+3>: and $0xfffffff0,%esp
- 0x080483ca <+6>: sub $0x20,%esp
- 6 short *p = 0;
- 0x080483cd <+9>: movl $0x0,0x18(%esp) // 0x18(%esp)为p在栈上所在地址
- 7 int *pp = 0;
- 0x080483d5 <+17>: movl $0x0,0x1c(%esp) // 0x1c(%esp)为p在栈上所在地址
- 8
- 9 ++p;
- 0x080483dd <+25>: addl $0x2,0x18(%esp)
- 10 ++pp;
- 0x080483e2 <+30>: addl $0x4,0x1c(%esp)
- 11 printf("%d %d\n", p, pp);
- 0x080483e7 <+35>: mov $0x80484d4,%eax
- 0x080483ec <+40>: mov 0x1c(%esp),%edx
- 0x080483f0 <+44>: mov %edx,0x8(%esp)
- 0x080483f4 <+48>: mov 0x18(%esp),%edx
- 0x080483f8 <+52>: mov %edx,0x4(%esp)
- 0x080483fc <+56>: mov %eax,(%esp)
- 0x080483ff <+59>: call 0x80482f4
- 12
- 13 return 0;
- 0x08048404 <+64>: mov $0x0,%eax
- 14 }
- 0x08048409 <+69>: leave
- 0x0804840a <+70>: ret
- End of assembler dump.
最后在简单的看一下普通的指针引用方法的例子
- #include <stdlib.h>
- #include <stdio.h>
-
- int main(void)
- {
- int a = 0;
- int *p = &a;
- int b = *p;
-
- printf("%d\n", b);
-
-
- return 0;
- }
下面看其反汇编的结果:
- 5 {
- 0x080483c4 <+0>: push %ebp
- 0x080483c5 <+1>: mov %esp,%ebp
- 0x080483c7 <+3>: and $0xfffffff0,%esp
- 0x080483ca <+6>: sub $0x20,%esp
-
- 6 int a = 0;
- 0x080483cd <+9>: movl $0x0,0x14(%esp) //0x14(%esp)为a在栈上所在的地址
-
- 7 int *p = &a;
- 0x080483d5 <+17>: lea 0x14(%esp),%eax //将0x14(%esp)的地址赋给eax
- 0x080483d9 <+21>: mov %eax,0x18(%esp) //将eax的值赋给0x18(%esp),即将a的地址赋给p,存在p所占的地址空间。
-
- 8 int b = *p;
- 0x080483dd <+25>: mov 0x18(%esp),%eax //将0x18(%esp)存的值赋给eax,这时eax存的是a的地址
- 0x080483e1 <+29>: mov (%eax),%eax //将eax的地址的值,赋给eax。这时eax存的是a的值
- 0x080483e3 <+31>: mov %eax,0x1c(%esp) //将eax的值赋给0x1c(%esp),即b。
-
- 9
- 10 printf("%d\n", b);
- 0x080483e7 <+35>: mov $0x80484d4,%eax
- 0x080483ec <+40>: mov 0x1c(%esp),%edx
- 0x080483f0 <+44>: mov %edx,0x4(%esp)
- 0x080483f4 <+48>: mov %eax,(%esp)
- 0x080483f7 <+51>: call 0x80482f4 <printf@plt>
-
- 11
- 12
- 13 return 0;
- 0x080483fc <+56>: mov $0x0,%eax
-
- 14 }
- 0x08048401 <+61>: leave
- 0x08048402 <+62>: ret
最后总结一下:指针本身也是一个变量,作为变量本身,其会占用内存空间,其内存空间同样可以存储数据,并且可以读取该数据。而作为引用使用时,即*p时,首先是需要读取其地址存储的数据,将其视为一个地址,再从这个地址中读取数据,作为结果。