欢从汇编角度看待数组名---纸上得来终觉浅,绝知此事要躬行

本文中心内容:1.数组名代表的是一个数据结构对象 2.数组名可以看成一个常量指针但实际并不是一个指针,3.指针存放的是偏移量(一个是对象,一个是可以看成指针但实际并不是指针,而另一个又是存放)。

刚上大学学C++的时候经常在网上看到文章说数组名就是指针。。然后就有人反驳,然后就在评论里面‘吵’个不停,’谁也不让谁。。。。无语
废话少说:

欲看懂后面的内容先补充几条AT&T形式的汇编语句:
1.mov $ 3 ,%eax :$ 表示3是一个立即数,把3放到eax里
2.mov 3 ,%eax :不带$符号表示此时是存储器寻址,把内存3号单元中的内容放到eax里
3.mov $3,(%eax):带括号表示把立即数3放到eax所指的内存单元中,可以把eax当作指针: *(%eax)=3;
4.不允许从存储器到存储器的mov指令.举个例子:把3号单元里的数据放到eax所指的内存单元的地址 不能使用 "mov 3,(eax) "必须分成两步:
mov 3,%ebx
mov %ebx,(%eax)
5.eax是32位的,而eax的低八位用al表示 (eax low)
6.汇编语言中符号名代表地址常数(分配了空间的,比如说数组名)或常数(比如说len equ 3 )
好了,开始我们的旅途:
先上一张图(本文核心)
在这里插入图片描述
(左边是c++代码,右边是生成的汇编代码,黑框框是输出)
先解释一下黑框框里的输出:
先看黑框框里的第二行和第三行:怎么变量和变量的地址会相同呢?其实这是因为第二行中是把myCharArray当作数组首地址来看的,而第三行中则是把myCharArray当作一个数据结构对象—数组来看的,这样数组首地址==数组对象的首地址不就解释得通了吗,既然把myCharArray当作数组对象来看,那么第一行得出size of myCharArray=10也就不奇怪了。同理可得:myCharPoint是一个指针类型的变量,于是size of myCharPoint=4便在意料之中。

。。。看来还得补充一点知识不然不知道该说什么了(顺便凑点字数):
关于全局变量和局部变量是什么时候分配的以及在哪分配的。
知识:局部变量在运行时分配,在栈上分配;程序在编译的时候也就是运行前就为全局变量分配好了空间,也就是说在程序运行前全局变量的地址就定死了,定死了,既然定死了,那么程序就可以用一个符号常量(数组名)来代表该变量的地址(数组首地址),于是便可以通过这个符号来引用这个地址进而引用整个数组。看上面的图:我们定义了一个全局数据结构myCharArray,从输出结果可以看出编译时为该数组分配的空间的首地址 0x47e000,在整个运行过程中这个数组会一直在这个地方,于是我们便用一个符号记录这个地址:

#define myCharArray 0x47e000 //#define 语句定义一个常数,并不分配内存

我们要将这个数组的第4个元素的值赋给vara,可以通过下面两个步骤
@1:mov mycharArray+3, eax //把数组第四个元素从内存中取出暂时放到eax中
@2:mov eax,vara //把元素从eax中取出放到为变量vara分配的空间中
图片右边:
0x401554~0x40155d -------------------- char vara=myCharArray[3];
0x40155e~0x401567 -------------------- char varb=myCharArray[5];
0x401568~0x401571 -------------------- char varc=myCharArray[7];
仔细观察图片中的指令中发现它直接使用确定的数值,确定的数值:
myCharArray=0x47e000
&myCharArray[3]=myCharArray+3=0x47e003
&myCharArray[5]=myCharArray+5=0x47e005
&myCharArray[7]=myCharArray+7=0x47e007
这说明了两点:1:这个数组的地址是0x47e000,2:数组地址在程序运行前就定死了定死了 3:用一个常数来表示数组首地址,并通过加上不同的偏移量来访问不同的数组元素(注释:编译器是不会为常数分配空间的(这涉及到计算机组成原理:立即数是直接存放在指令中的,而不是内存中(自行理解))

数组名我们暂且搁下,现在来看指针myCharPoint=“HelloWorld”;
图片左边2224行对应右边0x4015790x40159b
我们着重解释一下地址0x401579和0x40157e和0x401581的三条指令:
@0x401579:mov 0x47e00c,%eax //把地址0x47e00c里面的内容取出来放到eax中
@0x40157e:movzbl (%eax),%eax //把eax所指的内存单元中的内容取出来放到eax中(zbl 表示char-to-int ,不改变数值大小 )
@0x401581:mov %al,-0x11(%ebp) //把eax的低八位放到(char)vard中
这三条指令等价于 :
@1:int tmp=(&myCharPointer); //tmp=myCharPointer
@2:tmp=(int)(
(char*)tmp) //tmp=myCharPointer[0]=‘H’
@3:vard=(char)tmp //最终实现 vard=myCharPointer[0];
仔细观察图片(特别是黑框框第五行和第六行)并结合上面所说的我们可以得出这样一个结论:程序是直接用一个确定的值(0x47e00c)来记录全局变量myCharPointer的地址,并且通过@1来获取这个全局变量myCharPointer里的内容(47f024:字符串"HelloWorld"的地址),再通过(myCharPointer)来获取第47f024号单元里的内容’H’,也就是说 #define myCharPointer 0x47e00c //符号名代表地址
(至此,是否对指针有了更深刻的理解呢??(
__*) ?)

有什么能表示我说的不是错的呢?我们可以把编译时生成的汇编语言文件打出来(此时还未用数值代替符号,这样有助于更好的理解)

使用命令 : g++ -S main.cpp
在这里插入图片描述
再补充一个知识点:常说C++传递数组时数组名会退化成一个指针。其实它的意思就是我声明一个指针变量p,并且把myCharPoint的值赋给p,使得 p==myCharPointer,然后我们 在函数中p++,p-- 不就造成一种数组名退化成了一个指针的假象了吗?(对应上图中380~382)(o)/(实际上不管是传指针还是传引用实际上都是传值)
再看一张图:
在这里插入图片描述
我们想对数组名++,结果编译报错:
error :lvalue required increment operand
意思就是:自增运算符需要一个左操作数。那么这也就从反面证明myCharArray不是一个左值而是一个右值,是一个常数,不是吗?注意:++myCharPoint是可以通过编译的,因为myCharPoint有自己的空间,++myCharPoint可以分解为从地址取myCharPoint的值,然后++,然后将新值写回到地址里。

还想再展开讲一点:
虽然你用C++定义了一个数组,但是到了汇编语言层次便没有什么数组不数组的,有的只是一个一个的字节/字/双字。什么意思呢?意思就是你定义一个数组,然后编译器就根据数组大小分配一片空间,然后记录这录下这片空间的首地址(数组名myCharArray)。此后,汇编程序便只知道从myCharArray开始有一片空间可以用,但不知道这片空间有多大;虽然汇编程序不知道这片空间有多大,但程序员是知道的。也就是说你程序员知道这片空间有多大,你甚至知道这片空间中任意一个字节表示什么,并且可以保证不会越界访问。其实说白了就是任何地址其实都只是一个首地址,定义一个指针ptr*p=&mi只是将mi的起始地址赋给p而已,至于 代码中 var =*p 到底是取从地址p开始的一个字节还是两个字节还是n个字节则取决于var变量类型的大小。上图吧(?):
在这里插入图片描述

现在回过头去再想一下数组名,发现它只定义了一个名为myCharArray的符号常量,并没有分配空间来存储这个符号常量(再上一张图)
在这里插入图片描述

这下你可以明白C语言中为什么可以随心所欲的转换指针了吧,同时也可以明白为什么一个数组名既可以看成数组对象也可以看成数组首地址了吧。

综上:数组名只是一个符号常数,可以看成一个常量指针但并不是一个指针,不可以用来++ ,- - ,因为没有为它分配空间。这下应该可以分清数组名和指针了吧
(其实我也不知道他们是怎么实现数组的,我也是靠分析得出的结论,如果错了还恳请多多指教(如果真错了,那就真的尴尬了))

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值