函数原型
-
上面
proto
的大部分参数来源是lcode.c
,ldump.c
,llex.c
,lparser.c
,lundumpc
这几个文件赋予的 -
locvars
在这里并不指实际的局部变量,实际的局部变量
存在数据栈中,存的只是也写解析lua
文件以后的一些数据 -
upvalues
也不是指upvalues
变量,而实际存储他们的位置在CClosure->upvalue和LClosure->upvals
中 -
numparams
表示函数有几个参数,is_vararg
表示参数是否为可变参数列表,例如这个函数声明function f1(a1, a2, ...) ...... end
三个点
…
表示这是一个可变参数的函数.f1()
在这里的numparams
为2,并且is_vararg
的值为1. -
maxstacksize
字段 编译过程计算得到代表本proto
需用到的局部变量数量的最大值,从第一个型参开始计算(不包含不定参数...
因为哪个没法知道确切的数量 实际调用时传给不定参数...
的实参在L->func---->L->base
之间,数量在OP_VARARG
指令中已给出计算公式
变量的查找
局部变量,全局变量,上值其实在编译期就知道结果了主要是通过下面singlevar
和singlevaraux
两个函数确定
我们可以通过这两个函数的分析,可以看出,在查找变量的方式如下
总结
-
局部变量
存在数据栈中 -
上值
存在CClosure->upvalue和LClosure->upvals
中 -
全局变量
存在_ENV
中 -
在
Lua
中,函数参数也是局部变量 -
这里面还有一个
lua5.4
新增的新特性变量一个是TBC
局部变量,还有一个是const
局部变量-
const
局部变量-
从上面我们可以看到如果
const
类型的是只读数据,也就是赋值以后就确定下来了,不能在重新赋值local b <const> = 1 b = 2
我们从源码中可以看到有这样的函数
check_readonly
用来判断入股是const类型的变量
,而且要进行赋值,那么我们就报attempt to assign to const variable
这样的报错提示我们运行下上面的代码
发现的确如我们预想的那样
-
-
如果上述的代码,因为
const
类型变量是在编译时常量,不是指令类型的,所以我们也可以发现如果是下面这种类型的写法local b <const> = 1 c = b + 2
可以发现在
lua
在编译时就会直接把b
替换成1
,然后直接把表达式计算出来得到3
直接付给c
,这样做因为不是指令级别的,所以可以很大程度上的减少对寄存器的访问,所以如果发现自己的需求有大量的数值常量,比如游戏的配置表,就可以定义为const
局部变量类型反编译看下字节码
从上面我们可以看出并没有
b
变量的任何操作,然后再1
号位置直接把结果3
付给了_ENV.c
等价于_ENV.c=3
-
close
变量close
变量To-be-closed Variables
需要和close
元方法结合使用,在变量超出作用域时,会调用变量的close
元方法__close
,close
变量一般用于需要及时释放的资源的情况,多用这个其实也能减轻gc
的负担举个栗子
local t = {} setmetatable(t, {__close=function() print('To-be-closed Variables is closed') --接下来我们就可以在这个里面是否自己想要释放的资源 end}) local function func() local a <close> = t; end func()
通过如上图的源码的话,我们就可以调用
__closed
来释放对应资源了,当然如果你不写__closed
元方法,lua
也会给你相应的错误提示的
-
尾调用
function f()
…
return g()
end
因为调用g()
后,f()
中不再执行任何代码,所以不需要保留f()
的调用栈信息,Lua
做了这样的优化,称为尾调用消除
,g()
返回后,控制点直接返回到调用f()
的地方,有点类似c
语言的goto
语句,这样做也能减少栈的空间
lua函数种类
从lua
的源码中我们可以看出lua
一共分为3
种
宏 | 类型 | 注解 |
---|---|---|
LUA_VLCL |
Lua 闭包 |
用lua 脚本写的函数.有上值 ,运行时需要闭包LClosure ,函数原型Proto |
LUA_VLCF |
轻量C 函数 |
用C 写,没有上值 .运行时不须要闭包,函数原型lua_CFunction |
LUA_VCCL |
C 语言闭包 |
用C 写,有上值 .运行时需要闭包CClosure ,函数原型lua_CFunction |
闭包
C闭包
Lua在执行到fucntion ... end
表达式或者c层调用lua_pushcclosure
会创建一个函数对象,内存布局如下
lua闭包
lua
闭包和C
闭包不太一样它的动态内容里面不是Tvalue
类型而是UpVal
类型,为啥要这样类型呢,主要还是因为Lua
层的代码需要更多的信息处理,比如不同函数层次的调用,函数return
之后上值的处理,UpVal
还提供还原函数和子函数之间共享数据的方法
内存布局如下
upvalue
提供一种闭包之间共享数据的方法
UpVal
有两种状态open
状态 和close
状态
open
状态:还在lua
函数的block
中,所以是open
状态,并且放入L->openupval
链表当中,同时TValue *v
指针指向数据栈数据的位置close
状态:退出lua
函数的block
后,变成了close
状态,并且放入L->openupval
链表当中,同时TValue *v
指针指向TValue value
成员,也就是存放close
值得地方
闭包的创建
从上我们可以看出当虚拟机执行OP_CLOSURE
指令的时候,就进入luaF_newLclosure
函数创建一个lua
闭包,并根据proto->sizeupvalues
数量对上值进行填充,这里有个注意点,就是上值的填充会根据instack
标识来判断是直接从上值列表进行赋值,还是需要通过luaF_findupval
来从L->openupval
列表中查找上值,找不到创建一个新的upval
挂载入虚拟机openupval
-
instack
指明这个upvalue
会存在哪里,有两种情况要考虑:uv
如果是上一层函数的局部变量,且这个上层函数还在活动中,那么该局部变量一定还在上层函数的栈中.此时,instack
为1
,表明它在栈中,idx
指定在栈中的索引相对于上层函数的栈基址.uv
如果是上一层函数之外的局部变量,就像下面代码这样:local x = 1 local function func() local function innerfunc() return x + 1 end end
x
在上两层函数之外声明,Lua
是这样解决这个问题的:首先func
会把x
当成upvalue
记录下来,然后innerfunc
再从func
的upvalue
数组寻找.所以这种情况下,instack
为0
,则idx
表示上层函数uv
列表的索引.
闭包的关闭
当函数执行完毕会调用luaF_close
其实这里可以发现函数没有关闭时,引用的内存还是openupval
上,关闭后就重新赋值了一份,这个时候upval
就不是共享的了,每个闭包一份了
因为有upval
的存在这个也是因为lua
比较难正确性热更新的其中一个原因
upvalue的3中情况
upvalue不共享情况
function f1(n)
-- 函数参数也是局部变量
local function f2()
print(n) -- 引用外包函数的局部变量
end
return f2
end
g1 = f1(1979)
g1() -- 打印出1979
g2 = f1(500)
g2() -- 打印出500
这种情况其实就是因为f1
在退出自己的block
的时候重新赋值了一份,这个时候upval
就不是共享的了,而是单独存在了一份,这也说明了一个问题,当你在次调用f1
的时候,它因为不是共享所以不会在利用前一次调用f1
时候生成的upval
一个函数创建的闭包共享一份upvalue
function Create(n)
local function foo1()
print(n)
end
local function foo2()
n = n + 10
end
return foo1,foo2
end
f1,f2 = Create(1979)--创建闭包
f1() -- 打印1979
f2()
f1() -- 打印1989
f2()
f1() -- 打印1999
f1
,f2
闭包共享create
函数的局部变量n
同一闭包创建的其他的闭包共享一份upvalue
function Test(n)
local function foo()
local function inner1()
print(n)
end
local function inner2()
n = n + 10
end
return inner1,inner2
end
return foo
end
t = Test(1979)--创建闭包(共享一份upvalue)
f1,f2 = t()--创建闭包
f1() -- 打印1979
f2()
f1() -- 打印1989
g1,g2 = t()
g1() -- 打印1989
g2()
g1() -- 打印1999
f1() -- 打印1999
g1
和g2
与f1
和f2
共享同一个upvalue
.因为g1
和g2
与f1
和f2
都是同一个闭包t
创建的,所以它们引用的upvalue
(变量n
)实际也是同一个变量,而它们的upvalue
引用都会指向同一个地方
更详细的注释请去我的GitHub地址
以下是我几乎每行都加了注释的GitHub
地址