(转)基础概念:从概念及汇编角度解释指针本质

基础概念:从概念及汇编角度解释指针本质

Category: C/C++,基础概念 —  Feng @ 上午 10:02

 

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
今天一个同事问我一个关于指针的问题,从讨论中,我可以感觉到他对指针的认识还不是很清楚——当然,这不是说我自己认识就清楚,相对清楚而已。指针,大概是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类型,即引用类型;

上面是标准的定义,那么对于我来说,对指针的理解就是指针即变量。这句话听上去就是一句废话,不是变量还能是什么。但是也许有的朋友真的不能真正理解这句话。我在面试的时候,经常给应聘者出下面这道题目:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. int main(void)
  5. {
  6.     short *= 0;
  7.     int **pp = 0;
  8.  
  9.     ++p;
  10.     ++pp;
  11.     printf("%d %d\n", p, pp);
  12.  
  13.     return 0;
  14. }
如果对指针认识比较清楚的人,应该可以迅速说出答案。但是更多的候选人都是苦苦思索,想到程序在内存中的分布,作为elf可执行文件格式的起始地址为0×0804800,然后压栈去计算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。

下面看一下反汇编的结果:
  1. Dump of assembler code for function main:
  2. 5 {
  3. 0x080483c4 <+0>: push %ebp
  4. 0x080483c5 <+1>: mov %esp,%ebp
  5. 0x080483c7 <+3>: and $0xfffffff0,%esp
  6. 0x080483ca <+6>: sub $0×20,%esp

  1. 6 short *p = 0;
  2. 0x080483cd <+9>: movl $0×0,0×18(%esp) // 0×18(%esp)为p在栈上所在地址
  3.  
  4. 7 int *pp = 0;
  5. 0x080483d5 <+17>: movl $0×0,0x1c(%esp) // 0x1c(%esp)为p在栈上所在地址
  6.  
  7. 8
  8. 9 ++p;
  9. 0x080483dd <+25>: addl $0×2,0×18(%esp)
  10.  
  11. 10 ++pp;
  12. 0x080483e2 <+30>: addl $0×4,0x1c(%esp)
  13.  
  14. 11 printf("%d %d\n", p, pp);
  15. 0x080483e7 <+35>: mov $0x80484d4,%eax
  16. 0x080483ec <+40>: mov 0x1c(%esp),%edx
  17. 0x080483f0 <+44>: mov %edx,0×8(%esp)
  18. 0x080483f4 <+48>: mov 0×18(%esp),%edx
  19. 0x080483f8 <+52>: mov %edx,0×4(%esp)
  20. 0x080483fc <+56>: mov %eax,(%esp)
  21. 0x080483ff <+59>: call 0x80482f4 <printf@plt>
  22.  
  23. 12
  24. 13 return 0;
  25. 0×08048404 <+64>: mov $0×0,%eax
  26.  
  27. 14 }
  28. 0×08048409 <+69>: leave
  29. 0x0804840a <+70>: ret
  30.  
  31. End of assembler dump.

最后在简单的看一下普通的指针引用方法的例子
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. int main(void)
  5. {
  6.     int a = 0;
  7.     int *= &a;
  8.     int b = *p;
  9.  
  10.     printf("%d\n", b);
  11.  
  12.  
  13.     return 0;
  14. }
下面看其反汇编的结果:
  1. {
  2.    0x080483c4 <+0>: push %ebp
  3.    0x080483c5 <+1>: mov %esp,%ebp
  4.    0x080483c7 <+3>: and $0xfffffff0,%esp
  5.    0x080483ca <+6>: sub $0×20,%esp
  6.  
  7. int a = 0;
  8.    0x080483cd <+9>: movl $0×0,0×14(%esp//0×14(%esp)为a在栈上所在的地址
  9.  
  10. int *= &a;
  11.    0x080483d5 <+17>: lea 0×14(%esp),%eax //将0×14(%esp)的地址赋给eax
  12.    0x080483d9 <+21>: mov %eax,0×18(%esp//将eax的值赋给0×18(%esp),即将a的地址赋给p,存在p所占的地址空间。
  13.  
  14. int b = *p;
  15.    0x080483dd <+25>: mov 0×18(%esp),%eax //将0×18(%esp)存的值赋给eax,这时eax存的是a的地址
  16.    0x080483e1 <+29>: mov (%eax),%eax  //将eax的地址的值,赋给eax。这时eax存的是a的值
  17.    0x080483e3 <+31>: mov %eax,0x1c(%esp//将eax的值赋给0x1c(%esp),即b。
  18.  
  19. 9
  20. 10 printf("%d\n", b);
  21.    0x080483e7 <+35>: mov $0x80484d4,%eax
  22.    0x080483ec <+40>: mov 0x1c(%esp),%edx
  23.    0x080483f0 <+44>: mov %edx,0×4(%esp)
  24.    0x080483f4 <+48>: mov %eax,(%esp)
  25.    0x080483f7 <+51>: call 0x80482f4 <printf@plt>
  26.  
  27. 11
  28. 12
  29. 13 return 0;
  30.    0x080483fc <+56>: mov $0×0,%eax
  31.  
  32. 14 }
  33.    0×08048401 <+61>: leave
  34.    0×08048402 <+62>: ret

最后总结一下:指针本身也是一个变量,作为变量本身,其会占用内存空间,其内存空间同样可以存储数据,并且可以读取该数据。而作为引用使用时,即*p时,首先是需要读取其地址存储的数据,将其视为一个地址,再从这个地址中读取数据,作为结果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值