0.前言
您好,这里是limou3434的一篇个人博文,感兴趣的话您也可以看看我的其他文章。本次我将和您一起学习在C语言中函数栈帧的概念。
1.学习函数栈帧的意义
- 局部变量是怎么穿创建的?为什么局部变量的值是随机的
- 函数是怎么传参的?传参的顺序是怎么样的?
- 形参和实参是什么关系?
- 函数调用是怎么做的?函数调用时结束后怎么返回?
2.先不要使用太高级的编译器
编译器越高级就越难以观察到这些细节,因为有可能编译器做了非常高的封装,使得一些细节被其隐藏。但是使用新版本的编译器也行,有些时候大差不差。(例如本例中使用的VS2022在其汇编代码中,就有部分指令是VS2022自己加上的,这些指令对我们的学习暂时无关紧要,可以先忽略)
3.不同编译器函数调用中创建的栈帧有可能不同
在同时不同编译器下,函数调用的过程中栈帧的创建是有差异的,具体细节取决于编译器
4.计算机内的寄存器
计算机内部最常见的寄存器有“eax、ebx、ecx、edx”还有“ebp、esp”,最后两个寄存器存放的是地址,而这两个地址是用来维护函数栈帧的
5.调用main函数的函数
实际上是有函数来调用main函数的,这个函数就是“__tmainCRTStartup()”,而调用这个函数的函数是“mainCRTStartup()”,而调用这个函数的是操作系统
6.粗略解释函数栈帧的开辟和esp、ebp寄存器的使用
//源代码
#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\n", c);
return 0;
}
如果把函数栈帧简单理解,则对于上面的代码就对应下面的函数栈帧建立图示过程
但是如果仅仅是这么讲是远远不够的,接下来我们来试试读读一些相关代码的汇编代码(哪怕您没有学过汇编也不必担心,只需看懂个大概即可)
7.详细解释函数栈帧的开辟和开寄存器的使用
下面的汇编代码不用细看,只是整理出来让您结合图解来分析函数栈帧开辟的细节,您可以看完图解再回到汇编代码来复习
- C语言的源代码
//源代码
#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\n", c);
return 0;
}
- 上述源代码生成对应的汇编代码
//main函数内部的汇编代码
int main()
{
00B518B0 push ebp
00B518B1 mov ebp,esp
00B518B3 sub esp,0E4h
00B518B9 push ebx
00B518BA push esi
00B518BB push edi
00B518BC lea edi,[ebp-24h]
00B518BF mov ecx,9
00B518C4 mov eax,0CCCCCCCCh
00B518C9 rep stos dword ptr es:[edi]
00B518CB mov ecx,0B5C008h
00B518D0 call 00B5131B
int a = 10;
00B518D5 mov dword ptr [ebp-8],0Ah
int b = 20;
00B518DC mov dword ptr [ebp-14h],14h
int c = 0;
00B518E3 mov dword ptr [ebp-20h],0
c = Add(a, b);
00B518EA mov eax,dword ptr [ebp-14h]
00B518ED push eax
00B518EE mov ecx,dword ptr [ebp-8]
00B518F1 push ecx
00B518F2 call 00B513B6
00B518F7 add esp,8
00B518FA mov dword ptr [ebp-20h],eax
printf("%d\n", c);
00B518FD mov eax,dword ptr [ebp-20h]
00B51900 push eax
00B51901 push 0B57B30h
00B51906 call 00B510D2
00B5190B add esp,8
return 0;
00B5190E xor eax,eax
}
00B51910 pop edi
00B51911 pop esi
00B51912 pop ebx
00B51913 add esp,0E4h
00B51919 cmp ebp,esp
00B5191B call 00B51244
00B51920 mov esp,ebp
00B51922 pop ebp
00B51923 ret
//在调用Add函数时,其内部的汇编代码
int Add(int x, int y)
{
00221FF0 push ebp
00221FF1 mov ebp,esp
00221FF3 sub esp,0CCh
00221FF9 push ebx
00221FFA push esi
00221FFB push edi
00221FFC lea edi,[ebp-0Ch]
00221FFF mov ecx,3
00222004 mov eax,0CCCCCCCCh
00222009 rep stos dword ptr es:[edi]
0022200B mov ecx,22C008h
00222010 call 0022131B
int z = 0;
00222015 mov dword ptr [ebp-8],0
z = x + y;
0022201C mov eax,dword ptr [ebp+8]
0022201F add eax,dword ptr [ebp+0Ch]
00222022 mov dword ptr [ebp-8],eax
return z;
00222025 mov eax,dword ptr [ebp-8]
}
00222028 pop edi
00222029 pop esi
0022202A pop ebx
0022202B add esp,0CCh
00222031 cmp ebp,esp
00222033 call 00221244
00222038 mov esp,ebp
0022203A pop ebp
0022203B ret
图解1(__tmainCRTStartup函数调用main函数)
图解2
图解3
图解4
图解5
图解6(main函数调用Add函数)
图解7
图解8
图解9
图解10
图解11
图解12
图解13
图解14
图解15
图解16
……后续步骤我不再给出,如果您完整的看过上面的图解后,就能很清晰的理解栈帧这一概念了,也能对后续没有做图解的汇编代码进行理解
8.总结
这次我采用绘图的方式帮助您了解函数创立栈帧的详细过程,还希望您能仔细地看下去,这是一个C程序员内功的一部分。