1、为什么需要了解函数的栈帧
大家对于程序很少有人会对底层的东西刨根问底,很多人写程序大多就是写出来能够成功运行起来就大无所措了,但是很少有人对程序是怎么跑起来较为关心,比如:局部变量是怎么创建的;函数是怎么进行传参的等等,下面让我们来刨析一下其中的奥秘。
2、了解计算机中的寄存器
寄存器是植入在CPU中的一些小型存储区域,用来暂时存放参与运算的数据和运算结果,其运算速度极高,常见的寄存器有eax、ebx、ecx、edx、ebp、esp等等。其中ebp、esp寄存器是用来维护函数栈帧的,这两个寄存器和函数栈帧的创建和销毁密切相关。
3、以汇编语言为例进行刨析
我们以如下一个简单的加法实现代码的汇编语言为例来对问题展开分析,为使过程更加的清晰,我将代码的每一步都写的非常的细腻。代码如下: (注意:在不同的编译器下汇编语言可能存在差异!)
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 20;
int b = 11;
int c = 0;
c = Add(a, b);
printf("\n");
return 0;
}
首先我们明白main函数是C语言的主函数,所以的程序都是从main函数开始,在从main函数结束,但是一个单一的main函数不会独自的运行起来,所以main函数是怎么运行起来的哪? 在上面的图片中,地址:00C1210E,我们可以看到有一个 call _main (0C112D0h) 的指令,正是这个指令调用了main函数,让C语言程序运行起来了。下面继续看main函数的汇编指令
int main()
{
00C118E0 push ebp
00C118E1 mov ebp,esp
00C118E3 sub esp,0E4h
00C118E9 push ebx
00C118EA push esi
00C118EB push edi
00C118EC lea edi,[ebp+FFFFFF1Ch]
00C118F2 mov ecx,39h
00C118F7 mov eax,0CCCCCCCCh
00C118FC rep stos dword ptr es:[edi]
00C118FE mov ecx,0C1C003h
00C11903 call 00C1131B
int a = 20;
00C11908 mov dword ptr [ebp-8],14h
int b = 11;
00C1190F mov dword ptr [ebp-14h],0Bh
int c = 0;
00C11916 mov dword ptr [ebp-20h],0
c = Add(a, b);
00C1191D mov eax,dword ptr [ebp-14h]
00C11920 push eax
00C11921 mov ecx,dword ptr [ebp-8]
00C11924 push ecx
00C11925 call 00C110B4
00C1192A add esp,8
00C1192D mov dword ptr [ebp-20h],eax
printf("\n");
00C11930 push 0C17B30h
00C11935 call 00C110D2
00C1193A add esp,4
return 0;
00C1193D xor eax,eax
}
00C1193F pop edi
00C11940 pop esi
00C11941 pop ebx
00C11942 add esp,0E4h
00C11948 cmp ebp,esp
00C1194A call 00C11244
00C1194F mov esp,ebp
00C11951 pop ebp
00C11952 ret
从地址00C118E0的指令开始到00C118FE是对main函数栈帧的构建,并且对所构建的栈帧的内存空间进行了初始化,在该过程中先将ebp压入栈帧中,又将esp的值赋给ebp,再将esp的值加上0E4h通过这一系列的操作,使ebp维护栈底,esp维护栈顶,完成了对main函数栈帧空间的分配。又先后向main函数的栈帧压入了ebx、esi、edi,紧接着对空间进行了初始化,赋值为0CCCCCCCC。 从地址00C11908的指令开始到00C11916开始分别在ebp-8、ebp-14h、ebp-20h的位置创建变量a、b、c。 从地址00C1191D的指令开始到00C11924开始为函数传参,先传的b参数,又传的参数c,将参数拷贝到寄存器中。 想要了解更多内容,关注jiawen等待跟新,附上栈帧创建模拟图如下。
|