在反汇编观察后,你会发现:指针变量就是块内存区域,里面存放的是地址,你可以通过这个地址访问其它内存。
数组就是块连续的内存区域,里面连续排列着同样size的内存,多维数组也是一样的。
上述很简单,就不贴代码赘述了。
但人们一般纠结这样一个问题:数组名 是不是 一种指针?
也就是说arr[]的这个arr是不是一种指针?
这个问题之前csdn论坛上讨论的热火朝天:
http://bbs.csdn.net/topics/380226723
http://blog.csdn.net/yby4769250/article/details/7294718#reply
一种看法就是把这个数组名当作一种特殊的指针来看待,特殊之处在于是常量,不能改变其指向的位置。
一种是透过C语言,从C语言编译之后的汇编,视角来看,认为数组名和指针在内存中完全不是一回事。
太认真考虑其定义和概念就接近于一种“玄学”,我这里就单纯讲反汇编角度来分析 数组名 和 指针在内存中的差别吧。
随便写的代码:
#include <stdio.h> void func(int *p) { *(p+1) = 2; } void main() { int v = 1; int *p = &v; int v2 = *p + v; int arr[100]; int v3 = arr[0] + v; int v4 = *(arr+2) + v2; func(arr); }
void main() { 008114D0 push ebp 008114D1 mov ebp,esp 008114D3 sub esp,298h 008114D9 push ebx 008114DA push esi 008114DB push edi 008114DC lea edi,[ebp-298h] 008114E2 mov ecx,0A6h 008114E7 mov eax,0CCCCCCCCh 008114EC rep stos dword ptr es:[edi] 008114EE mov eax,dword ptr ds:[00818000h] 008114F3 xor eax,ebp 008114F5 mov dword ptr [ebp-4],eax int v = 1; 008114F8 mov dword ptr [v],1 int *p = &v; 008114FF lea eax,[v] 00811502 mov dword ptr [p],eax int v2 = *p + v; 00811505 mov eax,dword ptr [p] 00811508 mov ecx,dword ptr [eax] 0081150A add ecx,dword ptr [v] 0081150D mov dword ptr [v2],ecx int arr[100]; int v3 = arr[0] + v; 00811510 mov eax,4 00811515 imul eax,eax,0 00811518 mov ecx,dword ptr arr[eax] 0081151F add ecx,dword ptr [v] 00811522 mov dword ptr [v3],ecx int v4 = *(arr+2) + v2; 00811528 mov eax,dword ptr [ebp-1B4h] 0081152E add eax,dword ptr [v2] 00811531 mov dword ptr [v4],eax func(arr); 00811537 lea eax,[arr] 0081153D push eax 0081153E call func (0811226h) 00811543 add esp,4 }
可以看到arr[100]这个数组名arr在汇编代码中变成了一个单纯的标号,代表一个地址,这个地址是 数组这一列连续内存空间的首地址。
假设数组地址是N,那么数组名在汇编代码中就是N本身。
但对比来看,指针在这里是栈空间上申请的内存。这块内存里存着别的内存的地址。
假设指针的地址是M,指针指向的内存的地址是N,那就是地址为M的内存里存着N。
二者对比 数组名和指针的差别就清楚了吧。一个就只是标号(地址),一个是一块内存,内存里面存着地址。
上述是讲数组名 和 指针的差别。
下面要讲,
而当数组作为函数参数时,其实就是转化为指针来玩的。
先讲调用函数时的反汇编代码(这里取数组地址,然后压入栈再call func):
func(arr);
00811537 lea eax,[arr]
0081153D push eax
0081153E call func (0811226h)
00811543 add esp,4
再讲func函数里面对数组操作的反汇编代码:
上面的代码的函数func:
void func(int *p) { *(p+1) = 2; }
反汇编代码为:
void func(int *p) { 00FA3DE0 push ebp 00FA3DE1 mov ebp,esp 00FA3DE3 sub esp,0C0h 00FA3DE9 push ebx 00FA3DEA push esi 00FA3DEB push edi 00FA3DEC lea edi,[ebp-0C0h] 00FA3DF2 mov ecx,30h 00FA3DF7 mov eax,0CCCCCCCCh 00FA3DFC rep stos dword ptr es:[edi] *(p+1) = 2; 00FA3DFE mov eax,dword ptr [p] 00FA3E01 mov dword ptr [eax+4],2 } 00FA3E08 pop edi 00FA3E09 pop esi 00FA3E0A pop ebx 00FA3E0B mov esp,ebp 00FA3E0D pop ebp 00FA3E0E ret
这里形参为指针,没什么异议。
改写一下,形参改为,arr[]的话:
void func(int arr[]) { arr[1] = 2; }
void func(int arr[]) { 01173DE0 push ebp 01173DE1 mov ebp,esp 01173DE3 sub esp,0C0h 01173DE9 push ebx 01173DEA push esi 01173DEB push edi 01173DEC lea edi,[ebp-0C0h] 01173DF2 mov ecx,30h 01173DF7 mov eax,0CCCCCCCCh 01173DFC rep stos dword ptr es:[edi] arr[1] = 2; 01173DFE mov eax,4 01173E03 shl eax,0 01173E06 mov ecx,dword ptr [arr] 01173E09 mov dword ptr [ecx+eax],2 } 01173E10 pop edi 01173E11 pop esi 01173E12 pop ebx 01173E13 mov esp,ebp 01173E15 pop ebp 01173E16 ret
可以看出二者(不管形参写成*p还是arr[])是没差别的。
数组作为函数参数,都是会被转化为指针来操作。
为何?
因为调用函数前压入栈的是数组的地址,而不是整个数组。这个地址存在栈空间里,占有一定内存,所以成了一个指针!
函数里面从栈里获取到这个指针,继续操作。
这样是有一定道理的,因为数组可能很长,如果整个压入栈就是从新复制了一份空间,可能非常浪费,还不如指向前一个栈空间里数组的内存。
最后我想说,我玩这个反汇编,似乎没啥实际用处,比如就算不懂数组名 和 指针差别,也可以写程序啊。
但我总觉得,作为一个程序员,搞明白自己写的程序,到底是怎么被计算机运行的,会非常TMD爽。而不只是单纯的会写。
所以我最近决定系统的玩一把反汇编。
让自己对自己写的任何一行C、C++代码都对其运行原理、内存都一清二楚,看到C、C++代码就能迅速想象出汇编代码的样子。
下一个课题:彻底搞明白,各种数据类型在内存中的存储,又要涉及补码了。hhh