指针,地址,数组,让我们做做文字游戏

以下观点都是本人胡思乱想,在你看来可能是一派胡言,或者是毫无意义。尽管如此,我也尽量做到有理有据。

最近在拜读C语言进阶三部曲 <<C陷阱与缺陷>> ,<<C和指针>> ,<<C专家编程>>。<<C陷阱与缺陷>>已经完全读完了,<<C专家编程>>已经看了一大半,<<C和指针>>看了一部分。尽管还没有读完,但是这三本书已经把我的脑海搅得天翻地覆,让我迫不及待地想写下这篇文章,看一看有没有人和我有相同的感受。

首先来看<<C和指针>>里的一句话:

          指针只是地址的另一个名字罢了。指针变量就是一个其值为另外一个(一些)内存地址的变量。

                                                                                                  << C和指针>>中文版P33    

这段话作者清晰的表达了一个概念:指针只是地址的另一个名字罢了,也就是说指针的概念等效于地址的概念。而指针变量就是值为内存地址的变量。作者严格的区分了指针与指针变量,指针变量的值是地址,而指针的概念又等效于地址的概念,所以指针变量的值是指针。

但是我们再来看一句话,摘抄自被视为C程序员圣经的K&R<<C程序设计语言>>

          指针是保存变量地址的变量。

         <<C程序设计语言>>中文版 P93 

这句话作者完成了对“指针”的定义,指针是保存变量地址的变量。看出了什么区别吗?在<<C程序设计语言>>中指针就是一种变量,而在<<C和指针>>中作者认为指针就是地址的代名词。这就相当于<<C程序设计语言>>中”指针”的概念等同于<<C和指针>>中”指针变量”的概念。

再来看一段话,来自<<C陷阱与缺陷>>

         任何指针都是指向某种类型变量。例如,如果有这样的语句:

         int *ip;

         就表明ip是个指向整形变量的指针。又如果声明,

         int i;

         那么我们可以将整型变量i的地址赋给指针ip。

                                        <<C陷阱与缺陷>>中文版P35

这段话的观点和<<C程序设计语言>>的作者类似,而且对我们来说也非常亲切。他们都认为:指针就是一个变量,指针的值才是地址。看到这里,你可能觉得只是一些文字上的不同,有可能是<<C和指针>>的作者独树一帜。

让我们继续看一段话,同样来自<<C陷阱与缺陷>>

          除了a被用作运算符sizeof的参数情形外,在其他所有情形中数组名a都代表着指向数组a中下标为0的元素的指针。

                                                                                     <<C陷阱与缺陷>>中文版 P36


类似的文字你应该不止看到过一遍。这段话的意思告诉我们:除了数组名用作sizeof的参数情形外,表达式中的数组名都会被编译器当成指向该数组第一个元素的指针。让我们仔细分析这句话,按照<<C缺陷与陷阱>>的作者之前的观点:任何指针都是指向某种类型的变量。那么指向数组第一个元素的指针就代表是一个变量,而且该变量的值就是数组第一个元素的地址。所以数组名就被解析成一个变量,而该变量的值就是数组第一个元素的地址。一下一张图描述了这样的情形:


可能你已经迫不及待地说我错了,的确,上面的理解是错误的。对数组名的理解应该是这样的:


那为什么很多书上都讲”表达式中的数组名都会被编译器当成指向该数组第一个元素的指针“,因为这句话里的”指针“两个字代表的是地址的意思,而不是一个变量。 相当于这句话里的“指针”两个字又采取了<<C和指针>>里“指针“的概念。正是因为这句话里的”指针“就是”地址“的意思,所以”数组名是指向数组第一个元素的指针“想要表达的就是”数组名就是第一个元素的地址“。同样, <<C和指针>>里指针的概念也同样被混用,它没能坚持”指针只是地址的另一个名字“。例如<<C和指针>> 6.5节的标题

           6.5  未初始化和非法的指针

                     <<C和指针>> P95

这句话里的”指针“显然又代表的是变量的意思,因为只会有未初始化的变量,怎么会有未初始化的“地址”。

诚如你所看到的那样,即使<<C陷阱与缺陷>>,<<C和指针>>这样的大作,对指针的概念也存在混用的情况。洋洋洒洒地写了这么多,你可能不知道我在讲什么。我要表达的核心观点就是: 指针的概念已经被混用甚至是滥用了。有的书认为指针就是变量,指针的值才是地址,例如K&R的<<C程序设计语言>>, Kenneth A.Reek的<<C陷阱和缺陷>>。而有的书认为指针只是地址的另一个名字,而指针变量是存放地址的变量。例如<<C和指针>>,谭浩强的<<C程序设计>>(该书全篇都是认为指针是一个地址,而指针变量的值是地址(即指针))。但是即使在是同一本书里,指针也会有不同的含义。

虽然说指针的概念已经被众多的C语言书籍搞得模糊不清了,但是我们还是需要解决一个重大问题:指针和数组之间的关系。在讨论之前,为了避免继续模糊不清,将采用K&R的<<C程序设计语言>>的精确定义:

          指针是一种保存变量地址的变量。

              K&R<<C程序设计语言>>P93

          数组名代表数组首元素的地址

              K&R<<C程序设计语言>>P98

所以指针是指针,地址是地址,指针的值才是地址,而数组名代表的就是数组首元素的地址。

接下来让我们分析一个程序:

假设在一个源文件有以下代码:

int array[3]={1,2,3}; // define an array

在另一个源文件下有一下代码

#include <stdio.h>

int main()

{

extern intarray// declare an array

printf("%x\n",array[0]);

    return 0; 

}

程序运行之后,将会得到一个运行时错误。

 

接下来我们仔细分析该程序:

第一个源文件定义了一个数组:array, 它是一个外部对象,在第二个源文件中为了使用array这个外部对象,首先对它进行声明,但是声明时候却把array声明为一个指针。然后再对指针进行下标运算。那么为什么程序为什么会出错呢?关键原因是数组和指针并不相同。

让我们看以下例子:

int array[3]={1,2,3};

int *p=array;

array[0] 和 p[0]都可以访问到数组的第一个元素,但是这中间的步骤有很大的不同。

array[0]访问时,因为编译器知道array是个数组,也就是一个内存地址,所以为了计算array[0],由于array本身就是内存地址,所以运行时直接在array的基础上加上偏移量,就得到新的地址,再取出新地址里的内容即可。而如果通过p[0]访问时,由于编译器知道p是个指针,它存储的值才是一个地址。所以编译器首先根据符号表里p的地址,读取该地址里的内容,得到一个地址(指针存储的值就是个地址)再在得到的地址上加上偏移量,得到一个新的地址。读取新地址里的内容即可。

总结一下:不同于数组名本身就代表一个内存地址,指针存储的内容才是个内存地址。所以通过指针访问时,首先要在运行时读取该指针的内容。所以相比于数组名,指针需要增加一次额外的提取。

现在我们就能理解为什么上面的程序会出错:因为在我们把array申明为指针后,那么编译器就首先根据符号表里array对应的地址,读取该地址里的内容。我们知道array对应的地址就是数组第一个元素的地址,这个地址里存储了数组第一个元素。所以读取出来的值为1。接着马上把读取出来的内容当成一个地址,再加上偏移量(这个例子中是0)后得到一个新地址,最后读取新地址里的内容。所以程序最后读取地址为1的存储单元里的内容。这也就是为什么错误信息中显示: 读取位置为0x00000001时发生访问冲突。而我们只有把 array声明为数组时,该程序才能正确运行。

所以这也是为什么<<C专家编程>>里强调:

          在其他所有情况中,定义和声明必须匹配。如果定义了一个数组,在其他文件对它进行声明时也必须把它声明为数组,指针也是如此。

                                                                                            <<C专家编程>> 中文版P209


但是有一种情况:当作为函数的形式参数时,数组形参将会被当做指针。注意我用的词是”当做”,这意味着:int func (int array[ ]); 即使array是以数组的形式书写,在编译器看来array就是个指针,它的值是地址。而且这背后还有一层需要注意:因为C语言都是按值传递,array是个指针,也就是个变量。所以即使修改array值,也不会对实参做任何修改。例如以下程序:

#include <stdio.h>

int func(int array[])

{

++array;

return 0;

}

int main()

{

int  a[3]={1,2,3};

func(a);

}


尽管我们无法对数组名进行自增运算,因为数组名代表的是数组第一个元素的地址,所以相当于一个符号常量,当然无法改变它的值。但是因为C语言是按值传递,实参a 代表数组首元素的地址,而形参array是个指针,函数调用时形参array保存的是数组a首元素的地址的副本,但是array本身是个变量,当然可以进行修改。

总之:我们应该清楚地认识数组是数组,指针是指针。数组名本身就代表的数组首元素的地址,而指针的值才是一个地址。而作为函数形参的数组,会被编译器当做指针。


这篇文章整整写了一天,全是个人观点,有理解错误的地方,真诚希望阅读者能够不吝赐教!


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值