在x86-64系统上的函数调用过程(参考)

目录

1、介绍

1.1、传递控制权

1.2、传递数据

1.3、分配和释放内存

2、运行时栈的动作与转移控制

3、数据传送

3.1、参数传递

需要使用栈传递参数的情况

3.2、函数返回值传递


内容源自《深入理解计算机系统》(第三版)。为了提高函数调用的效率,x86-64系统引入了寄存器来传递函数参数。

1、介绍

        函数调用,也被称为过程,是程序设计中一种很重要的抽象。它提供了一个封装代码的方式,用一组指定类型的参数与一个可选的返回值实现某种功能,为外部提供了一个统一的接口,使程序只需要调用该接口(函数)就可以实现一类功能。并且就函数本身而言,其可以隐藏内部实现,具有保密功能。

        函数调用时系统会执行如下动作(假设过程P调用过程Q,Q执行完后返回到P):

1.1、传递控制权

        在进入过程Q时,也就是要执行Q的指令前,程序计数器就要被设置为Q的指令的起始地址(准备执行Q的代码)。并且要将原本程序计数器中保存的指令的下一条指令的地址保存下来,等Q执行完之后重设给程序计数器,恢复P的执行。

1.2、传递数据

        P在调用Q时,可能需要向Q传递若干参数,并且Q也可能向P返回一个返回值。

1.3、分配和释放内存

        在Q开始时,可能要为其中的局部变量分配空间,并且在返回时释放那些存储空间。

2、运行时栈的动作与转移控制

        C/C++在程序运行时使用了 数据结构: 提供的后进先出的内存管理原则,并且栈的增长是从高位地址地位地址方向的。将栈指针减少一个适量的值,就可以为函数或变量在栈上分配空间;同样,可以通过增加栈指针的值来释放空间

        在单线的运行过程中,在Q执行时,P及其以上的过程都是被暂时挂起的。

        在P调用Q时,P的控制和数据信息(也就是P当前指令的下一条指令的地址)被保存到P的栈尾,被称为返回地址,指明当Q返回时要从P程序的那个指令位置继续执行下去。Q的代码会扩展当前栈的边界,分配它的栈帧所需的空间。

        返回地址的压栈发生在 执行调用Q的指令 call Q 时。此时,Q代表Q程序指令的起始位置(被调函数的入口地址),系统先获取到PC(程序计数器)中当前指令的下一条指令的地址压入到P的栈帧中,再将PC的值置为Q的指令的起始位置(起始位置在call 指令时指明)。

        返回地址的出栈以及被重新设置给PC发生在 执行Q程序的最后一条指令 ret 时。此时先忽略返回值是如何返回的,只谈 ret 对PC的影响。ret执行后,栈指针%rsp向高位移动,将Q的栈帧释放当%rsp到达P栈帧中返回地址所在位置时,系统将返回地址的值赋给PC,%rsp继续向上移动,再释放掉为向Q传递参数开辟的空间(后续说明)。

        此时,系统根据PC保存的指令地址,继续执行P程序。这种把返回地址压入栈的简单机制能够让函数再稍后返回到程序中正确的点。

3、数据传送

3.1、参数传递

        在x86-64中,大部分的数据传送是通过寄存器实现的。

        为了提高函数调用的效率,x86-64系统引入了寄存器来传递函数参数。使用寄存器传递函数参数的好处是可以减少对内存的访问,提高执行效率。这是因为,寄存器的访问速度比内存的访问速度要快得多。此外,使用寄存器还可以减少栈的使用,从而减少栈指针的移动次数,进一步提高函数调用的效率。

        当参数小于 8字节时,就可以通过寄存器传参。

        用于数据传送的寄存器按照所传参数的位置分为六种:%rdi、%rsi、%rdx、%rcx、%r8、%r9(分别传递第1、2、...、6个参数,如果有的话),并且每种寄存器根据要参数大小,细分了四种:

        所以通过寄存器,最多可以传递6个参数,更多的参数就需要通过栈来传递。在P调用Q时,先将P中要传给Q的参数复制给要用到的对应的寄存器中,后续Q就可以通过对应的寄存器获取到P传递的参数。

需要使用栈传递参数的情况

        1.参数太多,超过6个,寄存器不够用了。

        2.传递栈帧中局部变量的地址(&变量名)(还不清楚为什么不能使用寄存器)

        3.参数为数组或较大结构体的引用时。

下面以传递7个参数的情况为例进行说明:

        P在P栈帧尾部,返回地址之后,开辟空间存储要传递给Q的参数,1~6按正常内存对齐方式存储,对于7~n个参数按照以8字节的对齐。

        此时栈指针就在最后一个参数之下的位置,对栈指针%rsp+8就能访问到第7个参数。

        若存在第8个参数,则栈指针%rsp+8就能访问到第8个参数,栈指针%rsp+16就能访问到第7个参数。

3.2、函数返回值传递

        若函数存在返回值,对于若返回值小于等于8字节,则使用%rax、%eax类的专门用来进行保存返回值的寄存器来传递。Q函数结束前,将返回值拷贝到%rax寄存器中,P函数通过访问%rax获得其值。

        对于大于8字节的返回值,如结构体、类的对象进行值返回时,会经历如下步骤:

1、P函数在自己的栈帧上提前额外开辟一块可以保存下来自Q返回值的栈空间,将这块空间作为接收返回值的临时对象空间,称为tmp。

2、P将tmp对象的地址作为隐藏参Q函数。

3、Q函数将数据拷贝给tmp对象,并将tmp对象的地址用%rax或%eax寄存器传出。

4、P函数根据寄存器中tmp的地址,将tmp的内容拷贝给实际用来接收返回值的变量。

5、释放tmp。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值