指针和数组的访问方式

本文转自:http://edsionte.com/techblog/archives/2475

指针和数组是不相同的,但“很多时候”我们总认为指针和数组等价的。不可否认,这两者在某种情况下是可以相互替换的,但并不能就因此而认为在所有情况下都适合。《指针和数组不是一回事儿》系列文章将逐步深入分析指针和数组的不同之处,并解释什么时候指数组等价于指针。本文属于《指针和数组不是一回事儿》系列文章之二。
前文从内存结构的角度说明了指针和数组的不同,本文将以访问方式的角度再次说明指针和数组的不同。先看下面的代码:

char str[] = "edsionte";
char *p = "edsionte";


当编译完程序后,程序中的标示符都有一个地址,所有标示符的地址形成一个符号表。数组的访问方式以数组str为例,如果要访问str[1],即数组str的第二个元素,则它的访问步骤如下:
1.从编译器的符号表中得到str的地址,比如0x0000FE00。这个地址即为数组str首元素的首地址;
2.在这个地址上加一个偏移量得到新的地址0x0000FE01;
3.从这个新地址中读取数据;
通过下图可以加深对数组访问方式的理解:

指针的访问方式以上述代码中的指针p为例,如果要访问*(p+1),即指针p所指向的匿名字符串的第二个字符,则它的访问步骤如下:
1.从编译器符号表中得到指针p的地址,比如0x0000EE20。这个地址即为&p,也就是指向指针p的指针;
2.从地址0x0000EE20中读取它内容即为指针p,比如0x00F0A000;
3.在0x00F0A000的基础上加一个偏移量,得到新地址0x00F0A001;
4.读取0x00F0A001中的内容,即为指针p所指的数据;
通过下图可以近一步理解指针的访问方式:

通过分析得知,在符号表中得到的是指针的地址而不是我们所要访问的指针;而在符号表中可以直接得到数组首元素的首地址。因此,访问指针时必须先通过符号表中指针的地址得到所要访问的指针,再接着进行指针所指内容的访问;而数组则直接可以通过符号表中的地址进行元素访问。也就是说指针的访问比数组的访问多了一次对内存地址的读取。
一不小心就引发的错误现在看下面的两段代码:

/* 代码段1 */
file1:
char str[] = "edsionte";
file2:
extern char *str;
/* 代码段2 */
file1:
char *p == "edsionte";
file2:
extern char str[];

对于代码段1,在文件1中定义了数组str,而在文件2中将str声明为指针;对于代码段2,在文件1中定义了指针p,而在文件2中将p声明为数组。这里的声明指的是外部引用型声明,定义指的是定义型声明。不管是上述那种情形,编译的时候都会出现错误。从上述对指针和数组访问方式的分析中可以得知,一个标示符被声明成指针还是数组对其访问方式影响巨大。下面我们对这两种错误作详细分析。
定义为数组,声明为指针在文件2中,既然str被声明成指针,那么就应当按照指针的方式进行访问。首先从符号表中得到指针str的地址;从该地址中读取4个字节的数据即为指针str;接下来根据指针str访问其所指向的数据。这个过程好像很顺利,不过对于文件1中的数组str,其访问过程又是怎样的?文件1和文件2的访问结果是否一致?下图可帮助你理解。

从上图可以看到,str在文件2中被声明成指针,那么就符号表中str的地址0x0000FE00会被当作指针str的地址。根据指针的访问方式,必须从这个地址中取出指针p。虽然以0x0000FE00为首的四个字节中存储的是“edsi”,但是它们一律会被当成地址,按十六进制表示即为0×65647379。即便可以访问到这个地址,但是从这个地址中按照char型取出的数据并不是我们想要的。
由于编译器对每个文件进行单独编译,文件2并不知道str在文件1中被定义成什么类型。str在文件1中被定义成数组,那么就应该按照数组的方式访问数据;str在文件2中被声明成指针,那么就应该按照数组的方式访问数据。因此,在两个不同的文件中分别将str定义成数组而声明成指针会出现对str访问不一致的现象,所以编译器会产生错误。
定义为指针,声明为数组此时,对于这种情况的理解也就简单多了。由于在文件而中p被声明成数组,因此就应该按照数组的方式对其进行访问。编译器会将原本指针str的地址当作str数组首元素的首地址,再对其加相应偏移量进行访问。这显然也是不合理的,因此编译器产生错误。具体可参见下图:

对上述的错误进行分析后,我们应该清楚将一个标示符声明成数组,编译器就会按照数组的访问方式去访问它;指针也是如此。因此,应该在多个文件中保持声明和定义相匹配。
指针和数组的其他区别指针和数组除了在内存构造和访问方式上不同外,还有一些其他的区别。
1.指针通常用于指向一个动态的数据结构,而数组则用于存储固定大小和数据类型相同的数据;
2.指针所指向的数据通过malloc()分配,并且需要free()释放;而数组本身的内存空间则是隐士分配和释放,也就是在定义数组的时候进行;
3.指针所指向的数据通常是匿名的,而数组名则是数组所占内存空间的名字;
在本系列的最后一篇文章中,我们将分析指针和数组易被混淆的根源——也可将其称为指针和数组的可交换性。
参考:
《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;
《C语言深度解剖》北京航空航天大学出版社;陈正冲 著;