当前位置: 首页 > news >正文

传参的理解

前言

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

文章汇编代码:采用 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 <mult2>:
  # a in %rdi,b in %rsi
  4004d2:   mov    %rdi,%rax	# 把 a 移动到 %rax
  4004d5:   imul   %rsi,%rax	# a * b
  4004d9:   retq

00000000004004da <mult_store>:
  # x in %rdi,y in %rsi,dest in %rdx
  4004da:   push   %rbx
  4004db:   mov    %rdx,%rbx		# 保存 dest,下文后讲为什么要这么做
  4004de:   callq  4004d2 <mult2>	# 调用 mult2
  4004e3:   mov    %rax,(%rbx)		# 将 %rax 里面的返回值
  									# 移动到 %rbx 保存的 dest 指针指向的内存处
  4004e6:   pop    %rbx
  4004e7:   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 <incr>:
  # p in %rdi,val in %rsi
  4004d2:   mov    (%rdi),%rax    
  4004d5:   add    %rax,%rsi    
  4004d8:   mov    %rsi,(%rdi)    
  4004db:   retq       
    
00000000004004dc <call_incr>:
  # x in %rdi
  4004dc:   push   %rbx
  4004dd:   sub    $0x10,%rsp		# %rsp 为栈顶指针,减小意味着为 call_incr 开辟 16 字节栈帧
  4004e1:   mov    %rdi,%rbx		# 保存参数 x 到 %rbx
  4004e4:   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>    # 调用 incr,此时 %rax 保存的值为 v2
  4004fc:   add    %rbx,%rax		# 将 x + v2
  4004ff:   add    $0x10,%rsp    	# 销毁栈帧
  400503:   pop    %rbx
  400504:   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;
};

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

相关文章:

  • 基于蜣螂算法的极限学习机(ELM)分类算法-附代码
  • 主流的操作系统(带你快速了解)
  • 六、numpy拷贝
  • STM32+python产生三角波
  • 【计算机网络(考研版)】第一站:计算机网络概述(一)
  • C++空间命名
  • 树,堆,二叉树的认识
  • 计算机存储系统
  • 返回值的理解
  • 前同事居然因为 Pycharm 的这个功能,即使离职三年也依然经常被请去喝茶~
  • IPV4地址详解
  • ubuntu 22.04学习笔记
  • 【蓝桥杯-筑基篇】基础数学思维与技巧(1)
  • 图论(入门版)
  • 使用bindgen将C语言头文件转换为Rust接口代码
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • 《剑指offer》分解让复杂问题更简单
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • golang中接口赋值与方法集
  • October CMS - 快速入门 9 Images And Galleries
  • spring boot 整合mybatis 无法输出sql的问题
  • 安装python包到指定虚拟环境
  • 服务器从安装到部署全过程(二)
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端临床手札——文件上传
  • 我感觉这是史上最牛的防sql注入方法类
  • 因为阿里,他们成了“杭漂”
  • 智能合约Solidity教程-事件和日志(一)
  • 7行Python代码的人脸识别
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • ​Python 3 新特性:类型注解
  • #FPGA(基础知识)
  • (0)Nginx 功能特性
  • (31)对象的克隆
  • (8)STL算法之替换
  • (二)JAVA使用POI操作excel
  • (二开)Flink 修改源码拓展 SQL 语法
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (七)Knockout 创建自定义绑定
  • (数据结构)顺序表的定义
  • (未解决)macOS matplotlib 中文是方框
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)Mysql的优化设置
  • (转)shell调试方法
  • .bat文件调用java类的main方法
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET设计模式(8):适配器模式(Adapter Pattern)
  • :=
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘
  • [ C++ ] STL---string类的使用指南
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决