lua 调用文件中的函数调用_深入Lua:调用相关的指令

前言

这一节我们来深入解析与调用相关的指令,这些指令是:

  • OP_CALL 调用
  • OP_TAILCALL 尾调用
  • OP_VARARG 可变参数
  • OP_RETURN 返回

解析这些指令的过程中,最重要的是时刻跟踪栈的变化情况。

简单调用

  • OP_CALL 的语法是:R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
    • R(A)为要调用的函数本身
    • 如果B=1,表示没有参数,如果B>1,表示有B-1个参数,这些参数从寄存器R(A+1)开始。
    • 函数调用完之后,如果C=1,表示没有返回值,如果C>1,表示有C-1个返回值,这些返回值会存到寄存器R(A)和它后面。从这里可以看出,本来存函数的R(A)最后被替换为返回值。
  • OP_RETURN 函数返回指令,语法是:return R(A), ... ,R(A+B-2)
    • 如果B=1,表示没有返回值,如果B>1,表示有B-1个返因值,这些返回值就存在寄存器R(A)和它后面。这和OP_CALL是相呼应的。

有了上面两个指令,就可以进行函数调用,先看下面的Lua代码:

1 
2 local function add(a, b)
3     return a + b
4 end
5 
6 local function div(a, b)
7     return a // b, a % b
8 end
9 
10 local function main()
11     local s = add(10, 20)
12     local d, v = div(s, 8)
13     print(d, v)
14 end
15 
16 main()

通过luac查看上面几个函数的操作码,在每行操作码的最后,我加上了栈的内容,用<>括起来,栈从函数对象开始,函数对象的后面为base, 操作码中的数字大多相对于base,比如0表示base自己,1表示base+1。

下面是main函数:

1       [11]    GETUPVAL        0 0     ; add  -- <main|add>
2       [11]    LOADK           1 -1    ; 10   -- <main|add|10>
3       [11]    LOADK           2 -2    ; 20   -- <main|add|10|20>
4       [11]    CALL            0 3 2          -- <main|30>
5       [12]    GETUPVAL        1 1     ; div  -- <main|30|div>
6       [12]    MOVE            2 0            -- <main|30|div|30>
7       [12]    LOADK           3 -3    ; 8    -- <main|30|div|30|8>
8       [12]    CALL            1 3 3          -- <main|30|3|6>
9       [13]    GETTABUP        3 2 -4  ;      -- <main|30|3|6|print>
10      [13]    MOVE            4 1            -- <main|30|3|6|print|3>
11      [13]    MOVE            5 2            -- <main|30|3|6|print|3|6>
12      [13]    CALL            3 3 1          -- <main|30|3|6>
13      [14]    RETURN          0 1            -- <>

下面是add函数:

1       [3]     ADD             2 0 1       -- <add|10|20|30>
2       [3]     RETURN          2 2         -- <30>

下面是div函数:

1       [7]     IDIV            2 0 1       -- <div|30|8|3>
2       [7]     MOD             3 0 1       -- <div|30|8|3|6>
3       [7]     RETURN          2 3         -- <3|6>

一开始main函数的调用信息和栈是这样的:

6144a2369e5eea8488ae0457f6335a66.png

执行了add函数之后,调用信息和栈变成这样:

670b738058d9df2ad1ea91a63e64cfe6.png

add函数返回,再调用div之后,调用信息和栈变成这样:

4726ab052ced24727bbc4dc8b11bf464.png

函数返回结果作为函数参数

把上面的Lua代码修改一下,变成下面这样:

local function add(a, b)return a + b
endlocal function div(a, b)return a // b, a % b
endlocal function main()local r = add(div(10, 4))local s = "sum=" .. rprint(s)
endmain()

变化之处是div函数的返回结果,直接作为add的参数。这一改变使得VM不知道add会得到多少参数,只能借助于div返回的栈顶。

OP_CALL的B和C有另一种情况,B=0时,参数从R(A+1)一直到栈顶;C=0时,返回值从R(A)一直到栈顶。借助这两种情况就能实现上面的逻辑,main函数如下:

1       [11]    GETUPVAL        0 0     ; add   -- <main|add>
2       [11]    GETUPVAL        1 1     ; div   -- <main|add|div>
3       [11]    LOADK           2 -1    ; 10    -- <main|add|div|10>
4       [11]    LOADK           3 -2    ; 4     -- <main|add|div|10|4>
5       [11]    CALL            1 3 0           -- <main|add|2|2>
6       [11]    CALL            0 0 2           -- <main|4>
7       [12]    LOADK           1 -3    ;       -- <main|4|sum=>
8       [12]    MOVE            2 0             -- <main|4|sum=|4>
9       [12]    CONCAT          1 1 2           -- <main|4|sum=4>
10      [13]    GETTABUP        2 2 -4  ;       -- <main|4|sum=4|print>
11      [13]    MOVE            3 1             -- <main|4|sum=4|print|sum=4>
12      [13]    CALL            2 2 1           -- <main|4|sum=4>
13      [14]    RETURN          0 1             -- <>

第5行CALL 1 3 0,C=0,表示返回结果从R(1)一直到栈顶;第6行CALL 0 0 2,B=0,表示add的参数从R(1)一直栈顶。

从VM代码看,调用div时,新的CallInfo的nresults等于-1,这表示函数的返回值为LUA_MULTRET;在div返回时,moveresults判断如果CallInfo的nresults等于-1,就返回函数的实际返回值,并且将L->top调整到n个返回值之后。紧接着下一条指令是对add的调用,就能根据L->top得到实际的参数。

可变参数

可变参数的指令是OP_VARARG:

  • OP_VARARG 语法是R(A), R(A+1), ..., R(A+B-1) = vararg,如果B>0,表示从可变参数接受B-1个参数,如果可变参数不满B-1个,则后面自动填nil。如果B=0,则将传进函数的所有可变参数赋值给R(A)...。

看下面代码:

local function main(...)print(...)return ...
endmain(10, "ok", false)

main函数的操作码如下:

1       [16]    GETTABUP        0 0 -1  ;   -- <main|10|ok|false|print>
2       [16]    VARARG          1 0         -- <main|10|ok|false|print|10|ok|false>
3       [16]    CALL            0 0 1       -- <main|10|ok|false>
4       [17]    VARARG          0 0         -- <main|10|ok|false|10|ok|false>
5       [17]    RETURN          0 0         -- <10|ok|false>

这里要注意一点是main函数的栈base是从可变参数之后开始的,即false后面的寄存器为0。

第2行VARARG 1 0, B=0,表示将所有可变参数保存到R(1)和后面的寄存器,然后设置好L->top

第3行调用print,B=0,所以参数就是从R(1)一直到L->top

第4行返回可变参数,B=0,表示将所有可变参数保存到R(0)和后面的寄存器,然后设置好L->top

第5行函数返回,B=0,表示将R(0)到L->top作为返回值。

尾调用

尾调用使用OP_TAILCALL指令,它和OP_CALL的不同之处是,这个指令不会生成新的CallInfo,它会重用调用者的CallInfo,因为尾调用只能在最后一条返回语句产生,在那一刻调用者的CallInfo已经使用完毕,所以可以重用这个CallInfo。尾调用只能是Lua函数,具体可看lvm.c的OP_TAILCALL指令处理。

其他方面和OP_CALL的含义基本一致,下面是一个例子:

local function div(a, b)return a // b, a % b
endlocal function calc(a, b)return div(a, b)
endcalc(10, 3)

calc里面的div调用就是一个尾调用,calc的指令如下:

1       [7]     GETUPVAL        2 0     ; div   -- <calc|10|3|div>
2       [7]     MOVE            3 0             -- <calc|10|3|div|10>
3       [7]     MOVE            4 1             -- <calc|10|3|div|10|3>
4       [7]     TAILCALL        2 3 0           -- <calc|10|3|3|1>
5       [7]     RETURN          2 0             -- <3|1>

第4行的C=0,表示返回值为从R(2)一直到栈顶。第5行的B=0,表示从R(2)一直到栈顶作为返回值。结合上面的例子,能得到这种情况一般都是将上一个函数的返回值作为当前函数的返回值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/428209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法基础——列表查找

whats the 算法 算法&#xff08;Algorithm&#xff09;是指解题方案的准确而完整的描述&#xff0c;是一系列解决问题的清晰指令&#xff0c;算法代表着用系统的方法描述解决问题的策略机制。也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输…

Python画板画图之美

Python画板画图之美 *turtle.done() #可让画板窗口停止*1.绘制同切圆 pensize为画笔宽度 circle(n),n为半径大小&#xff0c;两者单位均为像素 import turtle turtle.pensize(2) #画笔宽度&#xff0c;单位为像素 turtle.circle(10) #圆半径&…

python request库_【Python爬虫】Request库入门

什么是爬虫&#xff1f; 网络爬虫&#xff08;又被称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者&#xff09;&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还…

world文档粘贴图片进去看不到

在大学做实验报告的时候经常要插入一些截图&#xff0c;往往会遇到直接复制粘贴图片会看不到的情况&#xff0c;会很烦&#xff0c;我自己也找了好多方法&#xff0c;下面是我最喜欢的简便方法的一种。 解决方法如下: 可以点击鼠标右键选择段落&#xff0c;将行距修改为1.5倍即…

c盘users的用户名怎么改_怎么修改iPhone备份文件夹路径 iPhone C盘路径修改教程【详解】...

iPhone备份文件夹路径怎么修改_iPhone C盘备份路径修改教程 我们都知道iPhone默认的备份是在C盘&#xff0c;不过现在各种视频、照片体积那么大&#xff0c;小小的C盘只怕是负荷不了了&#xff0c;那么有什么办法去修改备份路径呢&#xff0c;下面小编就为大家介绍一下。需要注…

javaweb开发的准备工作——配置篇

1.配置 a. jdk配置&#xff08;用于web开发编程&#xff0c;此处不需要配置path&#xff0c;只需配置环境变量即可&#xff09; b. tomcat配置 打开Tomcat&#xff08;打开bin目录下的startup.bat文件&#xff09; 检验是否打开成功(两个网址都可以&#xff0c;出现汤姆猫即为…

阅读引擎开源项目调研总结

农历腊月初二&#xff0c;也是冬至后的第四个九天&#xff0c;俗称“四九”。冬至这一天开始数九&#xff0c;这就是人们所说的“提冬数九”。数上9天是一九&#xff0c;再数9天是二九……数到“九九”就算“九”尽了&#xff0c;“九尽杨花开”&#xff0c;那时天就暖了。《九…

ftp 上传文件夹_命令行连接FTP服务器

Windows下&#xff1a;打开命令行窗口&#xff0c;输入 ftp&#xff0c;进入ftp命令模式&#xff1a;输入 open ip地址 端口&#xff0c;进入ftp服务器&#xff0c;如open 172.16.3.77 2121。如下图&#xff1a;输入Windows下的用户名&#xff0c;然后输入密码&#xff08;注意…

创建实现一个简单的web项目

创建一个新的web项目 注意要选择Dynamic Web Project&#xff0c;第一次用可能要找一下&#xff0c;也可以直接搜索栏搜web&#xff0c;就会出来 图片是我自己照片照的&#xff0c;不好截图&#xff0c;看到有个人影勿慌哈哈哈哈&#xff0c;不是你眼睛的问题。 然后在web文件目…

eclipse xml文件报错_Maven教程6: Maven与Eclipse整合

点击上方“Java技术前线”&#xff0c;选择“置顶或者星标”与你一起成长一、安装Maven插件下载下来的maven插件如下图所示&#xff1a;&#xff0c;插件存放的路径是&#xff1a;E:/MavenProject/Maven2EclipsePlugin进入到eclipse中的dropins目录下&#xff0c;新建三个txt文…

C语言#define宏定义可能注意不到的地方

#define使用的核心:直接替换 我也觉得自己很清楚这一点&#xff0c;但看到这一道输出程序片段结果题&#xff0c;还是懵了。大家也可以在不看我下方答案的情况下&#xff0c;自己做一下&#xff0c;题目如下: #include<stdio.h> #define P 3 #define S(a) P*a*a int mai…

结合JSP与HTML做一个九九乘法表

不说了&#xff0c;先上效果图&#xff0c;下面附有源码: 代码一:jsp表达式法 <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitio…

python中parse是什么_Python中optparse模块使用浅析

最近遇到一个问题&#xff0c;是指定参数来运行某个特定的进程&#xff0c;这很类似Linux中一些命令的参数了&#xff0c;比如ls -a&#xff0c;为什么加上-a选项会响应。optparse模块实现的也是类似的功能&#xff0c;它是为脚本传递命令参数。 使用此模块前&#xff0c;首先需…

Topshelf创建Windows服务

入门例子 using log4net; using System; using System.Timers; using Topshelf;namespace ConsoleAppTest {class Program{static void Main(string[] args){log4net.Config.XmlConfigurator.Configure();HostFactory.Run(x >{x.Service<TownCrier>();x.RunAsLocalSys…

jsp网页中的换行

JSP网页中输出换行在一开始学习的时候困扰了我不短的时间&#xff0c;因为一些排版需要换行总是很难实现&#xff0c;随着学习&#xff0c;找到了如下几种方法做到换行&#xff0c;以保持页面的美观。 代码和截图我给放在下面了&#xff0c;附了注释: 代码如下&#xff0c;想…

npu算力如何计算_CPU、GPU、NPU、FPGA等芯片架构特点分析

来源 | 汽车电子与软件知圈 | 进“高精度地图社群”&#xff0c;请加微信15221054164&#xff0c;备注地图概述随着人工智能的热潮和AI算法的广泛应用&#xff0c;深度学习已成为当前AI研究的重点&#xff0c;在自动驾驶领域&#xff0c;环境感知、传感器融合、控制决策等等都会…

Eclipse——恢复的默认窗口设置篇

你可能也会像我一样&#xff0c;时不时不小心把一些窗口关掉了&#xff0c;然后想要改回来的时候&#xff0c;又要疯狂的去设置里把它们一个个的显示出来&#xff0c;大概率的还可能得百度一下&#xff0c;看着英文单词有时候就会烦躁。 直接崩溃&#xff1a; 这里一招教你直…

翻牌游戏如何打乱牌面java_家长专栏提高儿童记忆力的游戏训练

记忆是人脑对过去经验的保持和再现。记忆过程包括三个基本环节&#xff0c;即识记、保持、再认或回忆。识记是识别和记住事物。保持是将已获得的知识或经验巩固与保留在大脑中。再认是指过去经历过的事物再度出现时&#xff0c;能将它指认出来。回忆是指过去经历过的事物不在面…

Leetcode 206. Reverse Linked List

Similar Questions Reverse Linked List II Binary Tree Upside Down Palindrome Linked List思路&#xff1a;链表反转。 解法一&#xff1a;迭代。 添加头节点&#xff08;推荐&#xff09;&#xff1a;不断将当前元素start插入dummy和dummy.next之间&#xff0c;实现反转。…

java中输出系统时间

老用老忘&#xff0c;乐此不疲 自己给自己写个模板&#xff0c;忘了随时copy也很香&#xff0c;不是吗&#xff1f; 不说废话了&#xff0c;上代码: package java操作的复习;import java.text.SimpleDateFormat; import java.util.Date;public class SystemTime {public sta…