·局部变量是怎么创建的?
·为什么局部变量的值是随机值?
·函数是怎么传参的?传参的顺序是怎样的?
·形参和实参是什么关系?
·函数调用是怎么做的?
·函数调用后是怎么返回的?
解决这些问题前我们先来写一串代码
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d", c);
return 0;
}
eax、ebx、ecx、edx ebp、esp这些都是寄存器。ebp、esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。每个函数的调用都要在栈区创建一个空间。
当我们在vs2013测试运行这串代码的时候,打开调用堆栈的窗口,发现main函数又是被__tmain函数调用的
我们再转到反汇编代码中
#include <stdio.h>
int Add(int x, int y)
{
007613C0 push ebp
007613C1 mov ebp,esp
007613C3 sub esp,0CCh
007613C9 push ebx
007613CA push esi
007613CB push edi
007613CC lea edi,[ebp-0CCh]
007613D2 mov ecx,33h
007613D7 mov eax,0CCCCCCCCh
007613DC rep stos dword ptr es:[edi] //以上是在为Add函数调用栈帧
int z = 0;
007613DE mov dword ptr [z],0
z = x + y;
007613E5 mov eax,dword ptr [x] //x放到eax中
007613E8 add eax,dword ptr [y] //y和eax相加
007613EB mov dword ptr [z],eax //eax值放到z里
return z;
007613EE mov eax,dword ptr [z] //把z的值放到eax中去
}
007613F1 pop edi
007613F2 pop esi
007613F3 pop ebx
007613F4 mov esp,ebp
007613F6 pop ebp
007613F7 ret //就是返回到call指令的下一条地址去
------------------------------------------------------------------
int main()
{
00761410 push ebp //push是压栈
00761411 mov ebp,esp //mov是把esp给ebp
00761413 sub esp,0E4h //esp地址减去0EAh
00761419 push ebx
0076141A push esi
0076141B push edi
0076141C lea edi,[ebp-0E4h] //load effictive address是加载有效空间,把后边这个地址加载到edi中
00761422 mov ecx,39h //39h放到ecx中去
00761427 mov eax,0CCCCCCCCh //0ccccccch放到eax中去
0076142C rep stos dword ptr es:[edi] //edi地址开始到39h这些空间都改成0ccccccch
int a = 10;
0076142E mov dword ptr [ebp-8],0Ah //把0Ah这个数字放到ebp-8中
int b = 20;
00761435 mov dword ptr [ebp-14h],14h //把14h这个数字放到ebp-14h中
int c = 0;
0076143C mov dword ptr [ebp-20h],0 //把0这个数字放到ebp-20h中
c = Add(a, b);
00761443 mov eax,dword ptr [ebp-14h] //ebp-14h的值放到eax中去
00761446 push eax
00761447 mov ecx,dword ptr [ebp-8] //ebp-8的值放到ecx中去
0076144A push ecx
0076144B call _Add (07610E1h)
00761450 add esp,8 //就是直接返回到这里
00761453 mov dword ptr [ebp-20h],eax
printf("%d", c);
00761456 mov esi,esp
00761458 mov eax,dword ptr [ebp-20h]
0076145B push eax
0076145C push 765858h
00761461 call dword ptr ds:[769114h]
00761467 add esp,8
0076146A cmp esi,esp
0076146C call __RTC_CheckEsp (076113Bh)
return 0;
00761471 xor eax,eax
}
00761473 pop edi
00761474 pop esi
00761475 pop ebx
00761476 add esp,0E4h
0076147C cmp ebp,esp
0076147E call __RTC_CheckEsp (076113Bh)
00761483 mov esp,ebp
00761485 pop ebp
00761486 ret
push是压栈操作,esp地址由原来位置上移,如图所示
我们在监视窗口也可以看出esp地址发生变化
接下来就是mov是把esp给ebp,esp地址减去0EAh,
我们在内存窗口中可以看出这个空间是非常大的。
push ebx、esi、edi后如图所示
lea是load effective address是加载有效空间,把后边这个地址加载到edi中
mov把39h放到ecx中去0ccccccch放到eax中去 edi地址开始到39h这些空间都改成0ccccccch,如图所示
把0Ah这个数字放到ebp-8中 把14h这个数字放到ebp-14h中 把0这个数字放到ebp-20h中
原来的内存中放的是c,我们现在把10 20放到内存中,所以我们之前写代码的时候有时候会打印出来后边的烫烫烫汤乱码的现象,就是因为一开始存入的就是cccccc
当我们执行到call指令是就要开始调用Add函数了,此时如图所示
这一块代码是初始Add
如图所示
此时我们也会发现,a和b不是在Add函数栈帧里创建的,而是通过压栈回来找到的传参压进去的空间,所以我们之前说过形参是实参的一份临时拷贝是完全正确的
接着执行代码,返回值。最后执行ret指令时其实就是返回到call指令的下一条地址去,所以我们当时栈中存了一个地址
如有错误,还请指正