2025年函数调用堆栈、返回值与调用约定

函数调用堆栈、返回值与调用约定在看文章之前来了解两个指针 ebp 指向函数栈底的指针 esp 指向函数栈顶栈顶指针 include stdio h int sum int a int b int temp 0 temp a b return temp int main int stdio h

大家好,我是讯享网,很高兴认识大家。

在看文章之前来了解两个指针:
ebp:指向函数栈底的指针
esp:指向函数栈顶栈顶指针

#include<stdio.h> int sum(int a, int b) { int temp = 0; temp = a + b; return temp; } int main() { int a = 10; int b = 20; int ret = 0; ret = sum(a, b); printf("ret = %d\n",ret); return 0; } 

讯享网

一、函数堆栈的建立过程
接下我给程序打个断点,看一下执行时的部分汇编代码是什么样的:
1.main函数第一个大括号到函数第一条语句之间的汇编代码:

讯享网 17: int main() 18: { 00 push ebp 00 mov ebp,esp 00 sub esp,0E4h 00 push ebx 0015141A push esi 0015141B push edi 0015141C lea edi,[ebp-0E4h] 00 mov ecx,39h 00 mov eax,0CCCCCCCCh 0015142C rep stos dword ptr es:[edi] 19: int a = 10; # 这一段汇编程序是干什么的? main函数也是一个函数,它的执行是被其他函数调用的 这段汇编代码实际上是给main函数的执行开辟栈帧 00 sub esp,0E4h 指定了main函数的栈帧大小 大小为0E4h 00 mov eax,0CCCCCCCCh 0015142C rep stos dword ptr es:[edi] 循环的对main函数的栈帧进行初始化,初始值为0CCCCCCCCh 这就是我们打印没有初始化的变量看到的”烫烫烫“ 

2.再来看一下汇编层面上,局部变量是怎么被cpu拿到的:

 19: int a = 10; 0015142E mov dword ptr [ebp - 4],0Ah 20: int b = 20; 00 mov dword ptr [ebp - 8],14h 21: int ret = 0; 0015143C mov dword ptr [ebp - 0Ch],0 我们可以看到对a、b、ret等局部变量的时候不是在内存中取。 而是通过ebp指针的偏移来取的,不是操作 a b ret 等名字,因为他们都是指令 他们不会产生符号,保存在.text段 

3.sum函数被调用时的汇编代码

讯享网//这里展示的是压实参的过程 22: ret = sum(a, b); 00 mov eax,dword ptr [ebp -8] 00 push eax 00 mov ecx,dword ptr [ebp -4] 0015144A push ecx 0015144B call sum (015105Fh) 00 add esp,8 00 mov dword ptr [ebp - 0Ch],eax 着重看一下这两行代码: 下面两行汇编之前的汇编代码是分别将形参a b入栈 0015144B call sum (015105Fh) 00 add esp,8 call指令做了两件事情: 1.跳入sum函数 2.在进入sum函数之前,把call指令的下一行指令的地址入栈,即00 add esp,8 这样能够保证sum函数执行完以后,cpu知道自己该干嘛,pc寄存器将存储add esp,8 这一行的地址 
 10: int sum(int a, int b) 11: { 001513C0 push ebp 001513C1 mov ebp,esp 001513C3 sub esp,0CCh 001513C9 push ebx 001513CA push esi 001513CB push edi 001513CC lea edi,[ebp-0CCh] 001513D2 mov ecx,33h 001513D7 mov eax,0CCCCCCCCh 001513DC rep stos dword ptr es:[edi] 12: int temp = 0; # 这里的汇编是给sum函数的执行开辟栈帧,通过移动esp和ebp 001513C3 sub esp,0CCh 这行代码说明了sum栈帧的大小,移动了0CCH字节 001513D7 mov eax,0CCCCCCCCh 001513DC rep stos dword ptr es:[edi] 循环的对main函数的栈帧进行初始化,初始值为0CCCCCCCCh 这就是我们打印没有初始化的变量看到的”烫烫烫“ 

扩展:对于没有初始化堆上的内存,我们看到是”屯屯屯…”

5.sum函数的返回值如何带出去的呢:

讯享网 14: return temp; 001513EE mov eax,dword ptr [temp] 由于sum函数类型为int,返回值== 4B,我们可以看到汇编上是通过寄存器将temp带回去的 

6.sum函数的栈帧回退时,对使用过的内存进行处理了吗?

 15: } 001513F1 pop edi 001513F2 pop esi 001513F3 pop ebx 001513F4 mov esp,ebp 001513F6 pop ebp 001513F7 ret 实际上,我们能看到,sum函数在回退栈帧的时候,并没有对这块内存进行清零啊之类的操作 仅仅是esp回退而已 所以,有时我们尝试试图用非正常的去访问无效的内存可以看到有效的值,就不觉得奇怪了。 001513F6 pop ebp 通过该操作就知道主调函数的栈底地址,因为函数调用前call就把主调函数的栈底地址push进了被调函数的栈帧中。 

在这里插入图片描述
讯享网

二、函数堆栈的回退过程:
在这里插入图片描述

整个过程如上。

二、函数的返回值传递方式:

在函数开辟堆栈时,我们根据函数返回值的类型的大小,判断是否要产生临时量来将返回值带出去
<= 4B:一个寄存器带回
8B=< > =4B:通过两个寄存器带回
.>8B:通过产生临时量的方式处理返回值

1.函数调用前根据函数的返回值类型,确定函数返回值的大小
2.提前在主调函数的栈帧上开辟一块内存,然后调用函数的时候将这块内存的地址压栈进去,等到所以的实参入栈以后就将这块内存的地址入栈
3.被调函数产生的数据将根据临时量的内存的地址直接拷贝回主调函数

三、函数的调用约定

_cdecl:c调用约定,默认是该约定
_stdcall:windows标准的调用约定
_fastcall:快速调用约定
_thiscall:c++的成员函数的调用约定

1.函数产生的符号名字不同
2.函数参数的入栈顺序不同
3.谁来清理形参的内存

解答:
_cdecl:调用方开辟形参内存,调用方自己清理形参内存
_stdcall:调用方开辟形参内存,被调方自己释放形参内存
_fastcall:调用方开辟形参内存,但是把最后8个字节的实参通过寄存器带到被调用函数,被调方自己释放形参内存

讯享网int _cdecl sum(int a, int b) { //... } int _fastcall sum(int a, int b) { //... } int _stdcall sum(int a, int b) { //... } 
小讯
上一篇 2025-04-07 19:49
下一篇 2025-03-13 13:41

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/127504.html