总结:数组名和指针完全是两码事

  大家经常接触到诸如:“数组名可以当作指向数组首元素的指针用”、“指针可以用数组下标方式进行访问”这些事实,所以数组和指针的概念经常被混淆,其实数组和指针是完全不同的两种类型,下面本文分析一些常常被人们忽略的事实。

1.数组和指针是两种完全不同的类型

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *p_arr = arr;

  数组名arr代表着内存中有一块连续的区域,这个区域存储了10个int变量,它的大小在编译时已经确定(动态数组除外)。

  指针名p_arr代表着内存中的可以一个块,这个块的大小为足够表示地址的大小,这个块的内容是一个int变量所在的地址。

在内存中是这个样子:

Object           Address         0x00  0x01  0x02  0x03
------           -------         ----------------------
   arr           0x10008000      0x00  0x00  0x00  0x01
                 0x10008004      0x00  0x02  0x00  0x03
                 0x10008008      0x00  0x04  0x00  0x05
                 0x1000800c      0x00  0x06  0x00  0x07
                 0x10008010      0x00  0x08  0x00  0x09
  parr           0x10008014      0x10  0x00  0x80  0x00

【应用】  

这就解释了让人困扰的sizeof问题:

  为什么sizeof(arr)返回数组总大小,sizeof(p_arr)只返回指针大小?

  答:arr代表数组这块内存区域,编译后其大小就是固定的已知的,所以sizeof可以读出其大小。


2.数组和指针都可通过下标访问,但访问途径有所不同。

  上面的例子中,用arr[i]和p_arr[i]的效果完全一样。但是内部实现有所差别!

数组下标访问时arr[i]:
指针下标访问时p_arr[i]:

arr对应编译器符号表有个地址add0

运行时步骤一:取i的值与add0相加得add1=add0+i

运行时步骤二:取add1的内容

p_arr对应编译器符号表有个地址add0

运行时步骤一:取add0的内容add1=(add0)

运行时步骤一:取i的值与add1相加得add2=add1+i

运行时步骤二:取地址add2的内容

可见指针下标访问方式比数组多一次取地址步骤,即指针是间接访问、数组时直接访问。

【应用】

解释了:1).如果arr在一个文件中定义为数组,把它再另一个文件中声明为指针后,进行访问会错误。2).如果p在一个文件中定义为指针,把它在另一个文件中声明为数组后,访问仍会错误。

 

3.数组和指针下标符号的本质

 

定义:a[i] is identical to (*((a)+(i)))

 

当数组下标访问时arr[i],其实是这样:(*(arr+i))。arr+i运算时数组名arr就转换为了指向首元素的指针,所以会和指针下标有同样的效果。

 

这个定义也意味着:a[i]与i[a]其实是等价的,大家可以实验。

 

另外,对起始地址执行加法之前,编译器负责计算每次增加的步长。

如int *的指针p,p+1时会执行地址+4而不是+1。

所以,每种指针必须有一种类型的原因是:编译器需要知道 1)对指针解引用操作时应该取几个字节 2)下标的步长应该取几个字节

 

 

4.数组名不能转换为指针的情况

数组名在许多场合会自动转换为指向数组首元素的指针(函数传参、赋值给指针、表达式),但有些场合不会发生转换。

我所知道的有两种情况:sizeof操作符&取地址操作符

sizeof上面已经讲过。

我们看看arr和&arr的差别:

1 int arr[10] = {0,1,2,3,4,5,6,7,8,9};
2 int *p = arr;
3 int (*ap)[10] = &arr;
4 
5 printf("before: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);
6 p++;
7 ap++;
8 printf("after: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);

运行结果:

before:  arr = 0xbfbb96a0, p = 0xbfbb96a0, ap = 0xbfbb96a0
after:    arr = 0xbfbb96a0, p = 0xbfbb96a4, ap = 0xbfbb96c8

可以看出ap指向的是整个数组,当ap++后指针往后移动了40个字节。

 

 5.指针和数组在“定义时初始化”的不同

定义指针时,除了字符串常量的情况,编译器不为指针所指向的对象分配空间,只分配指针本身的空间。

char *p = "abcde";  //正确,但这个字符串存是只读的

int *p = 5;  //错误,不能给除了字符串以为的常量分配空间

定义数组时不存在这个问题。

 

6.数组名是不可修改的左值,指针是可修改的左值

左值:代表地址,表示存储结果的地方,在编译时可知。

右值:代表地址的内容,直到运行时才知。

数组名用于确定对象在内存中的位置,是左值,可是不能被赋值。如:

 

int arr1[10], arr2[10];
main()
{
    int *p;
    arr1 = arr2;  //错误,数组名不能被赋值
    p = arr1;  //正确,指针可以被赋值
}

 

 

 

 

 

posted @ 2013-10-23 20:24  任者  阅读(769)  评论(0编辑  收藏  举报