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

delphi中的函数传参如何传枚举参数_函数调用背后的秘密——传参与返回值

7e12b9affa14076e38fbbdad42c55467.png

函数调用是解耦思想的一个基本体现。为了实现调用,我们往往需要给子程序提供一些参数并得到子程序的执行结果。今天就来探究一下这个过程在计算机内部是如何实现的。更多内容欢迎来我的博客转转:

精神的壳​qiuyueqy.com
b4b78eb34271767ce582492e54ccfa9e.png

利用寄存器传参

来看例一:

int square(int num) {
    return num * num;
}

int main() {
    int k = 2;
    square(k);
}

在 x86-64 平台上汇编得到的结果是这样的:

square:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, eax
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 2
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    square
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret

变量 k 保存在内存 [rbp-4] 处,其值为 2。随后 [rbp-4] 中的值被存到了 eax,最后保存在 edi,这就完成了一次寄存器传参。square 函数更新 rbp 的值再从 edi 取值保存到 [rbp-4],完成形参到实参的复制。square 把结果保存在 eax 里,main 读取 eax 得到调用返回值。

用栈传参

当参数的数量过多无法全部保存到寄存器里或长度超过了机器字长,就要用到栈来传参。再来看例二:

typedef struct {
    char name[20];
    int  age;
}Person;

void greet(Person people) {
    printf("hello %s, you are %d years old.n", people.name, people.age);
}

int main() {
    Person me = {name: "Rowan", age: 21};
    greet(me);
}

翻译成汇编语言是这样:

.LC0:
        .string "hello %s, you are %d years old.n"
greet:
        push    rbp
        mov     rbp, rsp
        mov     eax, DWORD PTR [rbp+36]
        mov     edx, eax
        lea     rsi, [rbp+16]
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        movabs  rax, 474081619794
        mov     edx, 0
        mov     QWORD PTR [rbp-32], rax
        mov     QWORD PTR [rbp-24], rdx
        mov     DWORD PTR [rbp-16], 0
        mov     DWORD PTR [rbp-12], 21
        sub     rsp, 8
        push    QWORD PTR [rbp-16]
        push    QWORD PTR [rbp-24]
        push    QWORD PTR [rbp-32]
        call    greet
        add     rsp, 32
        mov     eax, 0
        leave
        ret

一个 Person 实例占 24 字节,超过了 x86-64 平台下一个通用寄存器的最大长度(8 字节),编译器用栈来保存参数。

26 到 28 行的三个 push 操作把 me 入栈,随后用 call 指令跳到 square 函数。square 函数在栈的 [rbp+36] 取到 age 成员的值,取 [rbp+16] (name 成员)的内存地址放入 rsi,完成一次传参。

细探 sp 与 bp

sp(stack pointer),中文名“栈顶寄存器”。顾名思义,这个指针时刻指向栈的顶部,其值随 push 和 pop 操作隐式改变。假设操作对象为 x:

  • push x:首先,sp -= len(x)(x 的字长,以字节为单位);然后,在 [sp-len(x), sp) 中存入 x 值。
  • pop x:首先,从 [sp-len(x), sp) 中读取数据存放到 x;然后,sp += len(x)。

如果编译器能推断一个函数要占用多少空间,在进入这个函数时往往会为它预先分配好一段栈空间(如例二汇编代码第 18 行 sub rsp, 32,为 main 函数分配了 32 字节栈空间)。在这个函数退出时,调整 sp 的值销毁这段空间(例二汇编代码第 30 行 add rsp, 32)。

bp(base pointer),中文名“基指寄存器”。因为随着指令执行 sp 是不断变化的,所以在进入函数时常用 bp 保存 sp 的初始值以对要用到的数据进行寻址定位(例二汇编代码第 6 行 mov eax, DWORD PTR [rbp+36],第 21 行 mov QWORD PTR [rbp-32], rax 等)。

指令 call x 会设置 ip 寄存器的值为 x 的段偏移地址,达到“调用函数”的目的。这个过程分为两步:

  1. 把 ip 寄存器的原址入栈(此时 ip 指向 call 的下一条指令),相当于 push ip
  2. 更新 ip 寄存器的值,相当于 mov ip, x

指令 ret 从栈顶取值保存到 ip 寄存器,相当于 call 的逆操作。执行完 ret 指令后 ip 恢复到函数调用前的值,程序从而继续向下执行。

指令 leave 在函数执行结束后恢复 sp 和 bp 的值。上文说到进入函数时会把 sp 保存到 bp 再调整 sp 的值以为函数分配栈空间。那么在退出函数时自然要恢复原来的 sp 和 bp。这条指令相当于 mov rsp, rbppop rbp 的结合。

相关文章:

  • python字符串处理函数汇总_超详细!盘点Python中字符串的常用操作
  • java接口应用在哪些方面_掏空了各大搜索引擎,整理了154道Java面试题!
  • flutter框架优缺点_2020年,一文点破跨平台开发框架现状
  • collect的功能是什么?其底层如何实现的?_用Python实现定时自动化收取蚂蚁森林能量,再也不怕被偷了...
  • docker python_「docker实战篇」python的docker-docker镜像的创建使用命令(32)
  • 在android studio中如何创建一个类来继承另外一个类_Python编程从入门到实践-连载8(类)...
  • python气象绘图速成_Python气象数据处理与绘图(11):矢量箭头图(风场,通量场)
  • ubuntu 自动挂载共享文件夹 /etc/fstab_使用 Cobbler 批量自动化部署 Windows 10 和 Windows Server 2019...
  • idea redis 插件_最全 !10个Redis可视化工具横向评测
  • sqlserver可视化工具_每个数据科学家都应该知道的18个基础工具
  • python coding_Python的编码注释# -*- coding:utf-8 -*-
  • python生成指定长度的列表_如何用Python创建固定长度的列表
  • 内存管理新技术_技术转管理,新项目经理都来看看
  • python中的json函数_Python Json模块中dumps、loads、dump、load函数介绍
  • char截取字符串_Java字符串:StringBuilder 和 StringBuffer
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【前端学习】-粗谈选择器
  • 2017 前端面试准备 - 收藏集 - 掘金
  • CentOS从零开始部署Nodejs项目
  • ES6语法详解(一)
  • interface和setter,getter
  • Java Agent 学习笔记
  • Java多线程(4):使用线程池执行定时任务
  • node 版本过低
  • opencv python Meanshift 和 Camshift
  • vue--为什么data属性必须是一个函数
  • Vue组件定义
  • 分布式事物理论与实践
  • 配置 PM2 实现代码自动发布
  • 前端js -- this指向总结。
  • 前端性能优化——回流与重绘
  • 深度解析利用ES6进行Promise封装总结
  • 实现菜单下拉伸展折叠效果demo
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 一、python与pycharm的安装
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 说说我为什么看好Spring Cloud Alibaba
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​比特币大跌的 2 个原因
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • # Apache SeaTunnel 究竟是什么?
  • # include “ “ 和 # include < >两者的区别
  • #微信小程序:微信小程序常见的配置传值
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (02)vite环境变量配置
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (floyd+补集) poj 3275
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (搬运以学习)flask 上下文的实现
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB