栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C/C++函数调用分析

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C/C++函数调用分析

C/C++函数调用分析
  • 1.调用惯例
  • 2.相关寄存器
  • 3.相关汇编指令
  • 4.代码分析
  • 5.函数返回值传递

1.调用惯例

调用惯例函数调用方和被调用函数对函数调用的统一理解,一般包含如下几个方面的内容:

  • 函数参数的传递顺序和方式: 最常见的方式是通过栈传递,也可以使用寄存器传递参数,提高性能
  • 栈的维护方式: 函数调用后,被压入栈中的参数需要被弹出,以使得栈在函数调用前后保持一致。这个弹出的工作可以由调用函数来完成,也可以有被调用函数来完成
  • 名字修饰策略: 不同的调用管理有着不同的修饰策略
调用惯例出栈方参数传递名字修饰
cdecl函数调用方从右至左的顺序入栈下划线+函数名
stdcall函数本身从右至左的顺序入栈下划线+函数名+@+参数的字节数
fastcall函数本身头两个类型存放到寄存器,其他剩下参数从右到左的顺序入栈@+函数名+@+参数的字节数
2.相关寄存器

x86_64中的常见寄存器和位数如下两幅图所示。

从图中可以看出64位是向前兼容32位,16位和8位寄存器的。

3.相关汇编指令

分析函数调用过程中的栈帧变化主要涉及到mov,push,pop,call,ret,leave这几条汇编指令,这几条指令的作用如下表所示

汇编指令作用
mov移动指令
push入栈指令
pop出站指令
call函数调用指令
ret返回指令
leave返回指令

有时候会在指令后面加上后缀,表明操作数的大小。常见的后缀有b,w,l,q,分别表示操作数大小位1字节,2字节,4字节,8字节。

call指令

call指令虽然是一条函数调用指令,但实际上它包含两步。第一步:将函数的返回地址入栈,即将函数调用后的第一条指令地址入栈;第二步:将被调用函数的地址装载入rip,从而跳转到被调函数。

ret指令

当执行ret指令时,rsp指向的栈顶被弹出值rip,完成被调函数返回

leave指令

leave指令是将栈顶指针指向帧指针,然后POP备份的原帧指针到%rbp,相当于如下两条指令:

mov %rbp %rsp
pop %rbp
4.代码分析
int sum(int num1, int num2, int num3, int num4, int num5, int num6, int num7, int num8) {
    int ans;
    ans = num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8;
    return ans;
}

int main() {
    int ans = sum(1, 2, 3, 4, 5, 6, 7, 8);
    return 0;
}

对其进行编译后,然后进行反汇编,得到对应的反汇编文件。

$ gcc func_call.c -o func_call
$ objdump -d func_call > func_call.txt

涉及到的相关核心部分如下所示:

5.函数返回值传递

从上述示例中可以发现,函数将返回值存储在eax寄存器中,然后函数的调用放再读取eax。eax本身只有4字节,那么大于4字节的返回值是如何传递的呢?

对于返回5-8字节的返回值,几乎所有的调用惯例都是采用eax和edx联合返回的方式进行的。eax存储返回值的低4个字节,edx存储放回置的高1-4字节。

对于字节数更大的返回值,是无法通过寄存器进行返回的。一种可能的实现方式是:先将返回值拷贝到栈上的临时区域内(临时对象),函数返回后再将该临时区域的值拷贝给函数调用方的接收变量。这也是为什么C++返回对象会调用两次拷贝构造函数的原因。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1026805.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号