数组名和指针那点事

mattuy 2018年02月05日 166次浏览

今天在查数组名的意义的时候,联系到指针的知识,对指针有了一种新的理解。

数组是一种特殊的变量,数组名具有双重含义(初始):

指向数组第一个成员的指针(如果是多维数组则数组成员也为数组);

代表数组本身(其实说成"指向整个数组的指针"更合适)。

一直对第二个含义不太理解,因为感觉上比较抽象,不知道有什么实际意义。

今天看到一个例子,终于开悟。

int ary[3];
printf("ary = %p\n" "ary + 1 = %p\n", ary, ary + 1);
printf("&ary = %p\n" "&ary + 1 = %p\n", &ary, &ary + 1);

输出结果:

ary = 0xffb38dc8
ary + 1 = 0xffb38dcc
&ary = 0xffb38dc8
&ary + 1 = 0xffb38dd4

这就比较有意思了,很明显,ary和&ary都指向了数组的首地址; ary+1相对ary增加了4,一个int型的长度,指向数组第二个成员首地址; 而&ary+1相对&ary则增加了3*4=12,正好是整个数组的大小。

因此数组名称的第二个含义是只有当数组名前有&时才体现(可能还有其他情况),单独使用时则相当于一个指向数组成员类型的指针(初始指向第一个成员首地址,另注意数组成员也可以是数组)。

所以对于一个一维数组int ary[3],它的数组名的含义有两层:

1):
int pAry = ary; //pAry复制了ary的第一层含义
// OR:
// int pAry = &ary[0];

2):
typedef int ARY[3];
ARY *pAry = ary; //pAry复制了ary的第二层含义

以前有一个疑惑。对于二维数组ary2[3][3],ary2,ary2[0]和&ary2[0][0]的值是一样的,都是整个数组的首地址。但*ary2的值竟然也是一样的,这是为什么呢?

先用之前的方法调试看看:

int ary2[3][3];
printf("ary2 = %p\n" "ary2 + 1 = %p\n", ary2, ary2 + 1);
printf("*ary2 = %p\n" "*ary2 + 1 = %p\n", *ary2, *ary2 + 1);

输出结果:

ary2 = 0xff9fa4a0
ary2 + 1 = 0xff9fa4ac
*ary2 = 0xff9fa4a0
*ary2 + 1 = 0xff9fa4a4

首先ary毫不意外地体现了数组名的第一层含义: 指向数组第一个成员的指针。由于是二维数组,所以数组的第一个成员也是一个数组,它拥有3个整型成员,大小是12字节。所以ary2+1相对ary2增加12。

那么*ary2中ary2到底体现的哪一层含义呢?*ary2的实际意义又是什么?

明显,ary2+1相对ary增加了4,一个整型的大小,所以ary2是相当于ary2[0]的第一层含义的,即指向ary2[0]的第一个成员ary[0][0]。反推回去,如果ary2中ary2是第二层含义(指向整个数组的指针)的话,那ary2应该得到的是整个数组的数据,而不是一个指针值。所以ary2中ary2是体现的第一层含义,即指向数组第一个成员(ary2[0])的指针。

但是这样解释新的问题又来了,运算符不是取出指针指向的值吗?那ary2[0]取出的应该是什么呢?应该是ary2[0]这个数组!但我们得到的结果呢?是一个int*型的指针!

这个问题折磨了我很久。它看起来并不重要,因为这不影响我们的实际操作,只要知道结论就够了。但强迫症还是让我被迫想出一个说服自己的说法:

问题的关键还在于开篇对数组名的解读。数组的名称可以当成数组本身的指针,也可以当成数组的第一个成员的指针。对于一维数组,这样的描述看上去没有问题,因为ary是可以取出数组第一个成员的值的,数组名像指针一样。但到了二维数组,情况就不一样了。直接取二维数组名ary2的值取出来的是ary2[0][0]的地址值,对数组名进行运算,*ary2取出来的还是ary2[0[0]]的地址值。

所以认为,编译器并没有为数组名在内存中分配空间,而是由编译器自己做了一个简单的映射,将数组名的值设为其第一个类型为数组的定义类型的成员的地址(比如int ary[][]的定义类型就是int),然后由编译器对不同的操作请求对数组名做出不同的解释。比如,对于三维数组int ary[2][2][2],编译器把标识符ary[i][i]的值设为ary[i][i][0]的地址值,而对于标识符ary[1],由于ary[1]的第一个成员ary[1][0]不是int型,所以继续拆分,直到找到第一个int型的成员ary[1][0][0],将ary[1][0]的值置为ary[1][0][0]的地址值。

由此类推,ary, ary[0], ary[0][0]它们的值是一样的!都是ary[0][0][0]的地址值!不同的只是它们各自管理的内存空间范围不同而已,并且它们管理的空间起点是相同的,都是ary[0][0][0]的首地址。注意,数组名的值都是直接被编译器记录的,而不是写入内存,所以是不能对数组名赋值的,对数组名的操作其实是对其具有内存空间的成员的操作。这点和指针不一样,因为指针本身其实是一个变量,它自己本身是有内存空间的,所以多级指针和多维数组变量名有本质区别!指针是一级一级的依次取值,才能得到最里层的数据,而数组是名依靠映射关系所以直接取得数据。