Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8104925
  • 博文数量: 159
  • 博客积分: 10424
  • 博客等级: 少将
  • 技术积分: 14615
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-14 12:45
个人简介

啦啦啦~~~

文章分类
文章存档

2015年(5)

2014年(1)

2013年(5)

2012年(10)

2011年(116)

2010年(22)

分类: C/C++

2011-06-26 21:38:00

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
    

今天,主要研究一下数组和指针的本质,即它们到底是什么?(下文都是我自己的理解,也许不够准确,欢迎大家拍砖)
请看下面的代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. int main()
  4. {
  5.     int array[4] = {0};
  6.     int *pointer = NULL;
  7.     int value = 0;

  8.     value = array;
  9.     value = &array;
  10.     value = array[0];
  11.     value = &array[0];
  12.     value = point;
  13.     value = &point;

  14.     return 0;
  15. }
下面是该C程序的汇编代码:
  1.   1 0x08048394 <+0>: push %ebp
  2.   2 0x08048395 <+1>: mov %esp,%ebp
  3.   3 0x08048397 <+3>: sub $0x20,%esp
  4.   /*
  5.   第4行至第7行对应C代码int array[4] = {0};
  6.   这里说明数组只是一个同类型变量的内存空间的集合。
  7.   对应这个例子,在栈上申请了4个整形变量的空间。
  8.   */
  9.   4 0x0804839a <+6>: movl $0x0,-0x14(%ebp)
  10.   5 0x080483a1 <+13>: movl $0x0,-0x10(%ebp)
  11.   6 0x080483a8 <+20>: movl $0x0,-0xc(%ebp)
  12.   7 0x080483af <+27>: movl $0x0,-0x8(%ebp)
  13.   /*
  14.   第8行对应int *point = NULL; 
  15.   这里说明指针本身自己也是一个变量,同样占用了栈的空间。
  16.   在这处要对比数组的处理,数组的占用的空间实际上是数组中的元素所占用的。
  17.   在本例中,即array[0],array[1],array[2],array[3],而array本身实际上更像一个label。
  18.   */
  19.   8 0x080483b6 <+34>: movl $0x0,-0x18(%ebp)
  20.   // 第9行对应int value = 0;
  21.   9 0x080483bd <+41>: movl $0x0,-0x4(%ebp)
  22.  /*
  23.  10~11行,对应代码value = array
  24.  因为array只是一个数组的名称,所以value = array就是将array数组的首元素的地址赋给value。
  25.  这个应该就是C标准的一个规则。这也是最合理的行为,因为如果不是取array数组的首地址,其它的行为更加没有意  义。
  26.  */
  27.  10 0x080483c4 <+48>: lea -0x14(%ebp),%eax
  28.  11 0x080483c7 <+51>: mov %eax,-0x4(%ebp)
  29.  /*
  30.  12~13行,对应代码value = &array
  31.  从C语言的语义上看,就是取array的地址。
  32.  从汇编上看,也是取array数组的首元素的地址。
  33.  */
  34.  12 0x080483ca <+54>: lea -0x14(%ebp),%eax
  35.  13 0x080483cd <+57>: mov %eax,-0x4(%ebp)
  36.  /*
  37.  14~15行, 对应代码value = array[0]
  38.  这里与上文不同,取数组的内容赋给value
  39.  */
  40.  14 0x080483d0 <+60>: mov -0x14(%ebp),%eax
  41.  15 0x080483d3 <+63>: mov %eax,-0x4(%ebp)
  42.  /* 
  43.  16~17行,对应代码value = &array[0]
  44.  很明显,取array[0]的地址赋给value
  45.  */
  46.  16 0x080483d6 <+66>: lea -0x14(%ebp),%eax
  47.  17 0x080483d9 <+69>: mov %eax,-0x4(%ebp)
  48.  /*
  49.  18~19行,对应代码value = pointer;
  50.  按照上文的说明,指针本身就是一个变量,所以这里就是将这个变量的值赋给value,即pointer的值赋给value,
  51.  也就是0.
  注意这里与value=array的区别。因为指针本身就是一个变量,所以value = pointer的合理语义就是将pointer的   值赋给value,而array本身并不是一个变量,所以value=array的语义,也是将array的数组首元素的地址赋给value。
  1.  */
  2.  18 0x080483dc <+72>: mov -0x18(%ebp),%eax
  3.  19 0x080483df <+75>: mov %eax,-0x4(%ebp)
  4.  /*
  5.  20~21行,对应代码value = &pointer;
  6.  取pointer的地址赋给value
  7.  */
  8.  20 0x080483e2 <+78>: lea -0x18(%ebp),%eax
  9.  21 0x080483e5 <+81>: mov %eax,-0x4(%ebp)
  10.  /*
  11.  22~24行,对应代码value = *pointer;
  12.  取poniter保存的值,将这个值作为一个地址,然后取该地址的值赋给value。
  13.  在这里,pointer的值是0,所以这句话的语义是从pointer取出它的值0,然后从地址0取值赋给value。
  14.  当然,如果执行到这里,程序会crash。因为地址0为非法地址。
  15.  */
  16.  22 0x080483e8 <+84>: mov -0x18(%ebp),%eax
  17.  23 0x080483eb <+87>: mov (%eax),%eax
  18.  24 0x080483ed <+89>: %eax,-0x4(%ebp)
  19.  22 0x080483f0 <+92>: mov $0x0,%eax
  20.  23 0x080483f5 <+97>: leave
  21.  24 0x080483f6 <+98>: ret
从上面的分析中,可以看出,尽管大多数情况下,数组和指针可以通用。但是由于对于&符号的不同的语义解释,在有&的情况下的代码,数组和指针是无法通用的。

请看下面的代码
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. static void show_str_pointer(const char **ppstr)
  4. {
  5.     printf("%s\n", *ppstr);
  6. }

  7. int main()
  8. {
  9.     char array[4] = "abc";
  10.     char *pointer = "abc";

  11.     show_str_pointer(&pointer);
  12.     show_str_pointer(&array);

  13.     return 0;
  14. }
执行结果为:
abc
7�
其中show_str_pointer(&pointer)的输出结果为我们所期望的,而show_str_pointer(&array)的输出为乱码。

其根本原因就是因为上面所揭示的数组和指针的本质不同, &array与array的语义相同。在这里指针与数组是不能互换的。&pointer为指针的地址,与show_str_pointer参数char**ppstr,指向指针的指针的变量类型相同。而&array仍然为数组地址,与参数char **ppstr的类型不符。

最后希望,大家从上面的分析和例子中,可以理解数组和指针的本质,以及何时它们之间可以互换,何时不能互换。


阅读(8847) | 评论(10) | 转发(16) |
给主人留下些什么吧!~~

Rainyzzj2011-08-29 18:14:03

非常感谢,今天面试就考到这个了,还好之前看过你的博文。谢谢啦。

GFree_Wind2011-07-21 18:53:51

SkyMei777: char array[4] = "abc";
    char *pointer = "abc";

    show_str_pointer(&pointer);
    show_str_pointer(&array);
这段代码.....
按照我另一个回复中所说,数组array[4],这个array与你所举的例子&a并不完全相同,并不是一个纯地址

GFree_Wind2011-07-21 18:51:11

SkyMei777: 如果这样可以,就可以定义
int a=10;
用&(&(&...(&a))),传地址、然后*(*(*...(*a)))来取值了。
显然这样行不通.....
是这个道理。

但是有一个问题是
比如int a = 10;
当使用&(&a)会编译出错,因为&a是一个地址,再次&&a时会出错。
而int a[] = {0};
如果编译器仅仅把a当作是一个地址的话,再次&a同样也应该报错。
所以对于数组a来说,并不是一个简单的地址。实际上对于数组来说array=&array

SkyMei7772011-07-21 17:04:37

如果这样可以,就可以定义
int a=10;
用&(&(&...(&a))),传地址、然后*(*(*...(*a)))来取值了。
显然这样行不通

SkyMei7772011-07-21 17:01:44

char array[4] = "abc";
    char *pointer = "abc";

    show_str_pointer(&pointer);
    show_str_pointer(&array);
这段代码是不是有问题?
array本身就是个地址,&array这种做法本身好像是取一个地址的地址,出错是必然的。就像是定义了一个变量,再取变量的地址的地址.
int a=10;
&(&a),这种做法,&(&a)就有问题了
不知道以上说法仅为探讨,不知道是否正确,如不正确,请指正。