传参的理解
创始人
2024-05-16 23:55:40

前言

当我们调用函数的时候,参数是怎么传递给被调用方的,有想过这个问题吗?传递不同大小的参数对调用方式有影响吗?本文将带你探究这些问题,阅读本文需要对函数栈帧有一定的理解,并了解基本的汇编指令。

文章汇编代码:采用 GCC 8.3.1,对 C 代码使用 -Og 优化级别生成的可执行程序,再用 objdump -d 反汇编的结果。

下表为文章使用到的一些基本汇编操作:

函数名参数操作
movSrc,DestSrc -> Dest(寄存器之间)
movSrc,(Dest)Src -> Dest 存储的内存地址处
addqSrc,DestDest = Dest + Src
subqSrc,DestDest = Dest - Src
imulqSrc,DestDest = Dest * Src

x86-64 中,4 字节操作后缀为 l,8 字节操作后缀为 q。

寄存器保存

如果参数比较小(4 or 8 bytes),在寄存器中可以放得下,那么前 6 个参数将被放在寄存器中(第一个在 %rdi,第二个在 %rsi,…),多的参数将被放在栈中保存。返回值存放在 %rax。

下图寄存器只能存放整数数据和指针,浮点数使用另外一组单独的寄存器。

存储参数

来看一段简单的代码:

// 一段简单让两数相乘的代码,写成这种形式,主要是尽量减少编译器优化
long mult2(long a, long b) {long t = a * b;return t;
}void mult_store(long x, long y, long* dest) {long t = mult2(x, y);*dest = t;
}
00000000004004d2 :# a in %rdi,b in %rsi4004d2:   mov    %rdi,%rax	# 把 a 移动到 %rax4004d5:   imul   %rsi,%rax	# a * b4004d9:   retq00000000004004da :# x in %rdi,y in %rsi,dest in %rdx4004da:   push   %rbx4004db:   mov    %rdx,%rbx		# 保存 dest,下文后讲为什么要这么做4004de:   callq  4004d2 	# 调用 mult24004e3:   mov    %rax,(%rbx)		# 将 %rax 里面的返回值# 移动到 %rbx 保存的 dest 指针指向的内存处4004e6:   pop    %rbx4004e7:   retq

保存参数

先看一段代码:

long incr(long* p, long val) {long x = *p;long y = x + val;*p = y;return x;
}long call_incr(long x) {long v1 = 2048;long v2 = incr(&v1, 1024);return x + v2;
}

看了上面的代码,你可能会有这样的疑惑:函数的第一个参数保存在 %rdi,call_incr 的 x 存储在 %rdi 中,在调用 incr 时,该寄存器的值已经被修改了,后面又会使用到 x,那该怎么办呢?

编译器有两种策略:

一种是调用方保存,后续我还会使用的参数都会保存在特定寄存器中,不管被调用的函数是否会修改。

一种是被调用方保存,使用时先将保存参数的寄存器的值存储到特定的寄存器,返回前修改回原状态。

下图为保存参数的特定寄存器:

调用保存

被调用保存

上图寄存器分类只是一种约定,编译器并不一定遵守,编译器可能有自己的使用分类。

00000000004004d2 :# p in %rdi,val in %rsi4004d2:   mov    (%rdi),%rax    4004d5:   add    %rax,%rsi    4004d8:   mov    %rsi,(%rdi)    4004db:   retq       00000000004004dc :# x in %rdi4004dc:   push   %rbx4004dd:   sub    $0x10,%rsp		# %rsp 为栈顶指针,减小意味着为 call_incr 开辟 16 字节栈帧4004e1:   mov    %rdi,%rbx		# 保存参数 x 到 %rbx4004e4:   movq   $0x800,0x8(%rsp) # 将 2024 存储到 %rsp + 8 处4004ed:   mov    $0x400,%esi    	# 将 1024 传到 %esi,即 %rsi 的低 32 位# movq 指令比 mov 指令占用的字节多4004f2:   lea    0x8(%rsp),%rdi   # 把 v1 的地址传到 %rdi,可以看到传参顺序是从右到左4004f7:   callq  4004d2     # 调用 incr,此时 %rax 保存的值为 v24004fc:   add    %rbx,%rax		# 将 x + v24004ff:   add    $0x10,%rsp    	# 销毁栈帧400503:   pop    %rbx400504:   retq 

编译器优化

上面讨论的都是比较小的内置类型,那假如对象很大,寄存器放不下该怎么办?

下面介绍一种 C++ 对参数的优化方式,实际编译器并不一定会使用该方式。

class qgw {// 有默认构造函数、拷贝构造函数、析构函数等long a1;long a2;long a3;
} qgw;void fun(qgw num);int main() {qgw tmp;...fun(tmp);return 0;
}

实际上,编译器可能创建一个临时变量,并修改函数的参数。转化结果可能为:

void fun(qgw& num);			// 修改参数为引用int main() {qgw tmp;...qgw __temp0;			// 创建临时对象__temp0.qgw::qgw(tmp);	// 调用拷贝构造fun(__temp0);__temp.qgw::~qgw();		// 销毁该临时对象return 0;
};

经过这样的转化,实际传递的参数变成了对象的地址,就可以保存到寄存器中了。

相关内容

热门资讯

猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
少数民族的传统节日有哪些 55... 公务员考试常识判断模块考查范围广泛,需要大家在日常多加积累,本文小编总结了中国少数民族的传统节日,希...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
mb什么意思 MB和Mb 相信很多人都还不太清楚手机流量的统计单位,经常听说谁谁流量包月5个G。其实G、GB、KB、M和MB数...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...