一、基础研究
将下面的程序编译连接,用debug加载:
首先执行第一条语句:
发现p=(unsigned char *)0x1000;在这里是把1000赋给一个偏移地址为01af、大小为两字节的内存空间。1000是一个值,这里将它强制转换成unsigned char *型的数据,否则赋给p会因为类型不同而出错。我们知道char型数据应该为1字节,那么为什么这里的数据大小为2字节呢?其实这里的1000是指针p的值,而我们定义的char是*p的值的大小,所以p的大小是两个字节,这个是由什么决定的呢?我们知道两个字节只能存储偏移地址而不能存储段地址,是指针只能指向本段的地址吗?
内存[01af]处的值为1000:
再看第二句是将偏移地址为1000、大小为1字节的内存空间的内容放到al里,为什么这里是地址为1000的空间的内容,而之前就是1000呢?因为之前是(unsigned char *)0x1000,而这里是*(unsigned char *)0x1000,前面的*表示以后面的值为地址的空间的内容。
之后将指针p也就是[1024]的值为偏移地址所指向的内存空间的内容加给al,我们注意到这里用到了bx寄存器来存储p的值,然后用[bx]将*p的值加到al里。
之后将bx清零赋给es,再给bx赋200,那么es:bx就是0:200,即(unsigned char far *)0x200,因为用了far,所以这里用es给出了段地址。之后把0:200地址处一个字节的空间内容加到al里,因为ch是全局变量,所以al只是暂存它的值,之后把al的值赋给ch的空间。
内存[01a8]处的值为7c:
下一句p=&ch;这里将ch的地址01a8赋给指针p的空间,即[01af]。&是取址符。
内存[01af]处的值为01a8:
下一句*p=*p+1;同样用寄存器bx存储p的内容,用al存储*p的内容,执行后内存[01a8]出的值为7d:
下一句pa=&p;对p取址,即对[01af]取址为01af,赋给pa,即[01a6],内存的值为01af;
下一句**pa=**pa+1;这里对pa用了两个*,即先由pa的值指向*pa,又由*pa的值指向**pa,对**pa进行加一操作。我们发现程序把[01a6]赋给bx,再把[bx]赋给bx,即达到两次*的作用,之后[bx]就是操作的对象。操作之后的内存[01a8]的值为7e:
我们发现根据地址值,这里**pa实际上就是*p,*pa实际上就是p,这是为什么呢?因为我们之前将p的地址给了pa所以pa的值实际上就是p的地址,我们用*对pa取值就会得到p的值,而对*pa取值就会得到*p的值。
下一句pf=(char far *)&ch;*pf是一个char far型的指针,对它的操作需要用到段地址。内存空间[01ab]的值为01a8:
下一句*pf=*pf+1;我们发现这里用les指令将内存中从01ab向后的4个字节中的低4位给了bx,高4位给了es。这高4位是哪来的呢?是之前mov [01ad],ds将ds的值保存为了高4位。为什么是ds的值?因为ch存储在本段里。如果ch存储在别的段里,这是是将别的段地址保存吗?
之后的流程和上面*p=*p+1很相似,只是用es:bx代替bx而已。内存空间01a8的值为:
下一句n=(int)&ch;ch的地址为01a8,给了n即[01a9]:
下一句*(char *)n=*(char *)n+1;这里向我们展示了使用指针的另一种方法,即将int型变量用(char *)强制转换成指针变量再来使用。内存空间[01a8]的值为80:
由这个程序我们发现*p就是取出p的内存空间的值为地址的内存空间的值,*是由寄存器bx来完成取值功能的,而&p是取出p的内存空间的地址。
长地址格式的转换形式,远地址格式。强制类型转换,只改变低四位。
再来看下面的程序:
程序中将结构体a的地址给了结构体指针pstu用的是pstu=&a;那么用*pstu=a可以吗?实验发现这样写的话,之后指针都指向错误的地址。因为这里应该是将a的地址赋给pstu,pstu才能指向a,而用*pstu=a则pstu的值没有改变,会指向错误的地址。
在原程序中对结构体指针的使用有两种方法:pstu->number=1和(*pstu).c=80,观察汇编程序可以发现这两种方式的结果是一样的。而bx的值始终为*pstu的值,在调用结构体数据项的时候会以bx+数值的方式访问。
之后我们对char型、int型和stu型的指针+1进行比较。对char型指针,pchar+1执行了一次inc ax语句,对int型指针,pint+1执行了两次inc ax语句,而对stu型指针,pstu+1是执行了add ax,000b语句,也就是将ax的值加了11,而stu所占的内存空间正好是11个字节。所以这里指针+1不是单纯地将指针的值加一,而是将指针的值加一个指针指向的数据类型的大小,也就是指向的地址向后移动一个存储的数据大小。
再来看下一个程序:
首先我们注意到汇编程序是怎么实现for循环的:这里n的地址为01ca,开始现进行一次判断,cmp byte ptr [bx+194],0.194是str的偏移地址相当于str[0],所以这里[bx+194]就相当于str[n]。如果str[n]不为0,则jnz会跳转到语句执行处,将str[n]赋给*(pf+n),之后n加1再进行判断。这里pf的偏移地址为01cc。
之后看下一个for循环的语句pf[n]=*(str+n);发现它的汇编语句与*(pf+n)=str[n]的汇编语句是一样的。也就是说pf[n]与*(pf+n)是等价的。在这样是因为c语言里对指针和数组的操作就是对地址的操作,他们具有相同的性质。
那么((int far *)0x220)[n]就相当于((int far *)0x220+n*2),这里n*2因为(int far *)0x220是一个地址而不是指针变量,而且这个地址的存储单位是int型即2个字节。
同样的观察汇编代码可以发现*(a+n)等价于*(&a[0]+n),即a与&a[0]是等价的。这是因为a[0]是数组的第一项,所以它的地址就是数组a的地址。
所以数组名就是一个指针,[]运算是在指针的值上加上一个偏移量,使其指向下一个存储单元的数据。
二、拓展研究
问题:
1、为什么指针存储地址的空间大小默认是2字节?
答:与编译器有关,16位就是2个字节,32位4个字节,64位8个字节。
2、如果要引用别的段的指针变量,是需要先将别的段的段地址保存,再用les命令吗?
3、指针:一个变量,对应一块内存,指针就是将存储的数据当地址用,而正常的变量将数据当数据用。
三、研究总结
这一章研究了指针机制,指针可以对内存进行操作,是c语言的精髓,研究时应多思考,否则很容易弄糊涂。