从指令角度掌握函数调用堆栈的详细过程

本文详细解析了函数调用的过程,包括参数压栈的从右到左顺序,函数执行完毕后的返回机制,以及32位操作系统下进程的虚拟地址空间布局。同时,介绍了函数调用惯例,参数传递方式,名字修饰策略,以及不同大小的函数返回值如何通过寄存器和临时对象进行传递。内容涵盖了函数调用的核心概念和技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看看如下代码:

int sum(int a,int b)
{
    int temp = 0;
    temp = a + b;
    return temp;
}
int main()
{
    int a = 10;
    int b = 10;
    int ret = sum(a,b);
    std::cout<< "ret :"<<ret<<std::endl;
    return 0;
}

看着上面的函数调用代码片段,我们有几个问题:

1.main函数调用sum,sum执行完毕后,怎么知道回到那个函数?

2.sum函数执行完后,回到main以后,怎么知道从哪一行代码开始执行。

        函数在运行的时候会在栈上开辟空间,函数调用时候参数压栈是从右向左。call指令首先会把下一行指令入栈,每个函数的左括号和右括号都是会产生指令的。最后返回会把下一个要执行的指令放到PC寄存器里面。

        nt a = 10;
008319C5  mov         dword ptr [a],0Ah  
int b = 20;
008319CC  mov         dword ptr [b],14h  
int ret = sum(a, b);
008319D3  mov         eax,dword ptr [b]  
008319D6  push        eax  
008319D7  mov         ecx,dword ptr [a]  
008319DA  push        ecx  
008319DB  call        sum (083118Bh)  
008319E0  add         esp,8  
008319E3  mov         dword ptr [ret],eax  
std::cout << ret << std::endl;
008319E6  mov         esi,esp  



int sum(int a, int b) {
00831940  push        ebp  
00831941  mov         ebp,esp  
00831943  sub         esp,0CCh  
00831949  push        ebx  
0083194A  push        esi  
0083194B  push        edi  
0083194C  lea         edi,[ebp-0Ch]  
0083194F  mov         ecx,3  
00831954  mov         eax,0CCCCCCCCh  
00831959  rep stos    dword ptr es:[edi]  
0083195B  mov         ecx,offset _FF03E44B_class1_1@cpp (083C0E1h)  
00831960  call        @__CheckForDebuggerJustMyCode@4 (083132Fh)  
int temp = 0;
00831965  mov         dword ptr [temp],0  
temp = a + b;
0083196C  mov         eax,dword ptr [a]  
0083196F  add         eax,dword ptr [b]  
00831972  mov         dword ptr [temp],eax  
return temp;
00831975  mov         eax,dword ptr [temp]  
}

再复习一下32位操作系统下面进程的虚拟地址空间布局:0-3G是进程私有的用户空间,3-4G是进程共享的内核空间。

 

 

函数的调用惯例:函数的调用方和被调用方对于函数如何调用必须要有一个明确的约定,只有双方都遵守这个约定,函数才能正确的被调用。

  • 函数参数的传递方式:最常见的函数参数传递是通过栈传递,函数的调用方将参数压入栈中,函数自己再从栈中将数据取出来。对于有多个参数的函数,参数压栈的顺序是从右到左。
  • 名字修饰(Name-mangling)的策略:为了连接的时候对调用惯例进行区分,需要对函数本身的名字进行修饰,不同的调用惯例有不同的名字修饰策略;

默认的调用惯例是:函数的参数传递是从右到左,在C语言中名字修饰是在函数名前面加上_下划线,而C++对函数进行名字修饰是则不一样,更为复杂;

  • 函数返回值传递:小于等于4字节的返回值通常是通过eax寄存器返回;在(4,8]之间的函数返回值通过两个寄存器eax和edx存储后返回;

大于8字节的,利用临时对象进行返回值的传递;

1、首先在mian函数中的栈上开辟一片额外的空间作为临时对象

2、调用函数时将该临时对象的地址通过隐藏的参数传递给函数

3、函数内部将返回值的数据拷贝到临时对象,并将临时对象的地址通过寄存器eax传出。

4函数返回后,将eax指向的临时对象的内存拷贝到main函数中的变量

参考书籍:《程序员的自我修养-装载链接与库》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值