Lua基础知识总结

转载的

1、Lua的基础工作原理,.lua文件实时编译之后,给到虚拟机的是什么指令.
具体指令形式有看吗?这个指令占了多少位数据,第n位主句代表啥,稍微看一下,有一个认识。 然后这些指令,具体怎么跟lua源码的模块代码相结合呢?比如我们是怎么调用到Talbe里面的add的? 其实每个指令具体执行,都有一个switch(指令类型)这样执行的,找到这个文件,然后有时间可以大概了解一下lua的文件结构,大概每个文件都放了一些啥,可以更深入了解一下。 lua源码(window项目)可以打开tolua_rumtime-master_5_3_2\lua-5.3.3\lua.sln来看
Lua使用虚拟堆栈向C传递值。此堆栈中的每个元素表示Lua值(nil,number,string等)。API中的函数可以通过它们接收的Lua状态参数访问此堆栈。
Lua运行代码时,首先把代码编译成虚拟机的指令(“opcode”),然后执行它们。 Lua编译器为每个函数创建一个原型(prototype),这个原型包含函数执行的一组指令和函数所用到的数据表。
虚拟机指令类型
/*
**虚拟机指令类型;;
**必须是无符号的(至少)4字节(请参阅lopcode .h中的详细信息)
*/
#if LUAI_BITSINT >= 32
typedef unsigned int Instruction;
#else
typedef unsigned long Instruction;
#endif
2、Lua的数据类型
(如果要看源码了,可以看一下会被gc的那个类型数据,是如何被定义的,为啥lua不需要定义数据类型就可以赋值?什么要的数据类型会被放到_G那里去。然后可能还有一些数据类型不不会暴露给我们使用的,比如Proto,这个跟function的实现相关,有兴趣可以了解一下。还有lua_State)
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
为啥lua不需要定义数据类型就可以赋值?
在赋值的时候,会调用函数expr解析表达式,=号右边的值,它最终会走入函数simpleexp中,在simpleexp中会根据expr解析出来的expdesc结构体里的t.token,用一个switch判断该表达式的类型,初始化expdesc结构体,将具体的数据赋值给expdesc结构体中的nval,所以,lua不需要定义数据类型就可以赋值,因为在解析器中会根据值的类型来进行初始化。

在函数localstat中,会读取“=”号左边的所有变量,首先看到在函数localstat中,首先会有一个循环调用函数new_localvar,将“=”左边的所有以","分隔的变量都生成一个相应的局部变量。 每一个局部变量,存储它的信息时使用的是LocVar结构体:

static void localstat (LexState ls) {
/
stat -> LOCAL NAME {’,’ NAME} [’=’ explist] */
int nvars = 0;
int nexps;
expdesc e;
do {
new_localvar(ls, str_checkname(ls));
nvars++;
} while (testnext(ls, ‘,’));
if (testnext(ls, ‘=’))
nexps = explist(ls, &e);
else {
e.k = VVOID;
nexps = 0;
}
adjust_assign(ls, nvars, nexps, &e);
adjustlocalvars(ls, nvars);
}

typedef struct LocVar {
TString varname;
int startpc; /
first point where variable is active /
int endpc; /
first point where variable is dead */
} LocVar;
这里主要存储了变量名,放在该结构体的变量varname中。一个函数的所有局部变量的LocVar信息,是存放在Proto结构体的locvars中。 在函数localstat中,会读取“=”号左边的所有变量,创建相应的局部变量信息在Proto结构体中。

我们从通过lua_pushbollean等指令函数看,c通过这些函数将各种类型的值压入lua栈,从而传递给lua。

(lapi.c) 556行
LUA_API void lua_pushboolean (lua_State L, int b) {
lua_lock(L);
setbvalue(L->top, (b != 0)); /
ensure that true is 1 */
api_incr_top(L);
lua_unlock(L);
}

(lobject.h) 225行
#define setsvalue(L,obj,x)
{ TValue *io = (obj); TString *x_ = (x);
val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt));
checkliveness(L,io); }
可以看到从虚拟栈里取出top之后,把值传给了setbvalue(L,obj,x)。

而在 setbvalue 里,obj 被转换成了 TValue 类型,接着又调用了两个宏 val_(),settt_()来设置 TValue 类型的两个成员。

由此可见,lua 栈中所有类型的值都是用 TValue 结构体来表示的。

那么TValue结构体是什么样的呢?
(lobject.h) 110行
#define TValuefields Value value_; int tt_

typedef struct lua_TValue {
TValuefields;
} TValue;
它由一个实际的 value 和一个int类型的 tag 组成。

基本类型
(lua.h)
/*
** basic types
*/
#define LUA_TNONE (-1) // 无类型
#define LUA_TNIL 0 // 空类型
#define LUA_TBOOLEAN 1 // 布尔
#define LUA_TLIGHTUSERDATA 2 // 指针 (void *)
#define LUA_TNUMBER 3 // 数字 (lua_Number)
#define LUA_TSTRING 4 // 字符串 (TString)
#define LUA_TTABLE 5 // 表 (Table)
#define LUA_TFUNCTION 6 // 函数 (CClosure)
#define LUA_TUSERDATA 7 // 指针 (void *)
#define LUA_TTHREAD 8 // LUA虚拟机 (lua_State)
value_ 是一个 union 类型 Value,所以它可以存储多种类型的值,根据注释可知全称叫Tagged Values。

(lobject.h 100行)
/*
** Tagged Values. This is the basic representation of values in Lua,
** an actual value plus a tag with its type.
*/

/*
** Union of all Lua values
*/
typedef union Value {
GCObject gc; / collectable objects */
void p; / light userdata /
int b; /
booleans /
lua_CFunction f; /
light C functions /
lua_Integer i; /
integer numbers /
lua_Number n; /
float numbers */
} Value;
Lua内部用一个宏,表示哪些数据类型需要进行gc操作的: (lobject.h)

#define iscollectable(o) (rttype(o) & BIT_ISCOLLECTABLE)

/* TValue的原始类型标签*/
#define rttype(o) ((o)->tt_)

/可收集类型的位标记/
#define BIT_ISCOLLECTABLE (1 << 6)

#define rttype(o) ((o)->tt_)
可以看到,tt_的第六位用于标记类型是否需要进行垃圾回收,

可进行垃圾回收的类型:GCObject
/*
** Common type has only the common header
*/
struct GCObject {
CommonHeader;
};

#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
可以看到GCObject结构中只有一个CommonHeader,CommonHeader主要由一个指向下一个回收类型的指针,一个对象类型tt和一个对象标记marked组成。

所以,lua中所有类型都的结构示意图如下:

TValue 里不是已经有一个 tt_ 字段用于表示类型了吗?为什么在 GCObject 里还需要这个字段呢?
答:要从 GCObject 反向得到 TValue 是不行的,假如 GCObject 没有 tt 字段,单单持有 GCObject 的时候,没法判断这个 GCObject 的类型是什么。 GC 在回收对象的时候需要根据类型来释放资源。基于第一点,必须在 GCObject 里加一个表示类型的字段 tt。

作者:董哒哒 链接:https://www.jianshu.com/p/ad30f77bd7d6

3、为什么说Lua一切皆Table,Table有哪两种存储形式,Table是如何Resize的
Lua的table是由数组部分(array part)和哈希部分(hash part)组成。数组部分索引的key是1~n的整数,哈希部分是一个哈希表(open address table),哈希表本质是一个数组,它利用哈希算法将键转化为数组下标,若下标有冲突(即同一个下标对应了两个不同的键),则它会将冲突的下标上创建一个链表,将不同的键串在这个链表上,这种解决冲突的方法叫做:链地址法。
table 最基础的作用就是当成字典来用。 它的 key 值可以是除了 nil 之外的任何类型的值,当把 table 当成字典来用时,可以使用 pairs 函数来进行遍历,使用pairs进行遍历时的顺序是随机的,事实上相同的语句执行多次得到的结果是不一样的。
当 key 为整数时,table 就可以当成数组来用。而且这个数组是一个 索引从1开始 ,没有固定长度,可以根据需要自动增长的数组,我们可以使用使用 ipairs 对数组进行遍历。
其他语言提供的所有结构—数组,记录,列表,队列,集合这些在lua中都用table来表示。
向table中插入数据时,如果已经满了,Lua会重新设置数据部分或哈希表的大小,容量是成倍增加的,哈希部分还要对哈希表中的数据进行整理。需要特别注意的没有赋初始值的table,数组和部分哈希部分默认容量为0。
resize代价高昂,当我们把一个新键值赋给表时,若数组和哈希表已经满了,则会触发一个再哈希(rehash)。再哈希的代价是高昂的。首先会在内存中分配一个新的长度的数组,然后将所有记录再全部哈希一遍,将原来的记录转移到新数组中。新哈希表的长度是最接近于所有元素数目的2的乘方。
local a = {} --容量为0
a[1] = true --重设数组部分的size为1
a[2] = true --重设数组部分的size为2
a[3] = true --重设数组部分的size为4

local b = {} --容量为0
b.x = true --重设哈希部分的size为1
b.y = true --重设哈希部分的size为2
b.z = true --重设哈希部分的size为4
4、Lua的面向对象实现
所以,实际上,class.new是什么呢?然后new完之后,返回的是什么东西?
使用元方法模拟面向对象的实现
–[[
云风的lua面向对象编程架构,用来模拟一个基类
–]]

local _class={}

function class(super)
local class_type={}
class_type.ctor=false
class_type.super=super

--[[
模拟构造函数的function
--]]
class_type.new=function(...) 
        local obj={}
        do
            local create
            create = function(c,...)
                --如果本类存在着基类,就递归调用基类的创建函数初始化基类的成员
                if c.super then
                    create(c.super,...)
                end
                -- 如果本类有构造函数,就执行本类的构造函数操作
                if c.ctor then
                    c.ctor(obj,...)
                end
            end
            --前面的这段代码是声明create function,下面的就是执行
            create(class_type,...)
        end
        --将此对象的元表的__index元方法设为下面的虚函数表
        setmetatable(obj,{ __index=_class[class_type] })
        return obj
    end

-- 用一个table来构造类的函数表
local vtbl={}
_class[class_type]=vtbl

–[[
设置表class_type的元表并定义__newindex字段,字段对应的函数,
参数1就是表class_type本身,当添加一个新方法的时候就会执行此__newindex的实现
–]]
setmetatable(class_type,{__newindex=
function(t,k,v)
vtbl[k]=v
end
})

--[[
如果本类有父类的话,将本类虚函数表的原表__index设从父类的函数表,直接从父类的函数表中查找。
--]]
if super then
    setmetatable(vtbl,{__index=
        function(t,k)
            local ret=_class[super][k]  --这里,就是查找父类的函数表的操作
            vtbl[k]=ret
            return ret
        end
    })
end

return class_type

end
5、Lua元表是什么?
元表主要用于对两个table进行操作,例如两个table相加,当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"add"的字段,若找到,则调用对应的值。“add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法”。
6、Lua的gc机制简述
在Lua5.0及其更早的版本中,Lua的GC是一次性不可被打断的过程,使用的++Mark算法是双色标记算法(Two color mark)++,这样系统中对象的非黑即白,要么被引用,要么不被引用,这会带来一个问题:在GC的过程中如果新加入对象,这时候新加入的对象无论怎么设置都会带来问题,如果设置为白色,则如果处于回收阶段,则该对象会在没有遍历其关联对象的情况下被回收;如果标记为黑色,那么没有被扫描就被标记为不可回收,是不正确的。
为了降低一次性回收带来的性能问题以及双色算法的问题,在Lua5.1后,Lua都采用分布回收以及++三色增量标记清除算法(Tri-color incremental mark and sweep)++
将所有对象分成三个状态:
White状态,也就是待访问状态。表示对象还没有被垃圾回收的标记过程访问到。(白色又分为White0和White1,主要为了解决上面所说到的在GC过程中新加入的对象的处理问题)
Gray状态,也就是待扫描状态。表示对象已经被垃圾回收访问到了,但是对象本身对于其他对象的引用还没有进行遍历访问。
Black状态,也就是已扫描状态。表示对象已经被访问到了,并且也已经遍历了对象本身对其他对象的引用。
GC流程:
每个新创建的对象颜色设置为White
//初始化阶段
遍历root节点中引用的对象,从白色置为灰色,并且放入到Gray节点列表中
//标记阶段
while(Gray集合不为空,并且没有超过本次计算量的上限):
从中取出一个对象,将其置为Black
遍历这个对象关联的其他所有对象:
if 为White
标记为Gray,加入到Gray链表中

//回收阶段
遍历所有对象:
if 为White,
没有被引用的对象,执行回收
else
重新塞入到对象链表中,等待下一轮GC
在每个步骤之间,由于程序可以正常执行,所以会破坏当前对象之间的引用关系。black对象表示已经被扫描的对象,所以他应该不可能引用到一个white对象。当程序的改变使得一个black对象引用到一个white对象时,就会造成错误。解决这个问题的办法就是设置barrier。barrier在程序正常运行过程中,监控所有的引用改变。如果一个black对象需要引用一个white对象,存在两种处理办法:
将white对象设置成gray,并添加到gray列表中等待扫描。这样等于帮助整个GC的标识过程向前推进了一步。
将black对象改回成gray,并添加到gray列表中等待扫描。这样等于使整个GC的标识过程后退了一步。
这种垃圾回收方式被称为"++Incremental Garbage Collection++"(简称为"IGC",Lua所采用的就是这种方法。使用"IGC"并不是没有代价的。IGC所检测出来的垃圾对象集合比实际的集合要小,也就是说,有些在GC过程中变成垃圾的对象,有可能在本轮GC中检测不到。不过,这些残余的垃圾对象一定会在下一轮GC被检测出来,不会造成泄露。

7、Lua的全局变量跟local变量的区别,Lua是如何查询一个全局变量的,local的作用域
Lua将所有的全局变量保存在一个常规的table中,这个table称之为环境(_G),使 用下面的代码可以打印当前环境中所有全局变量的名称
for n in pairs(_G) do
print(n)
end
在Lua中,要声明全局变量很简单,那就是定义变量的时候,前面不要加上 local。这个神秘的全局环境,其实本质上也是一个table,它把我们创建的全局变量都保存到一个table里了。而这个table的名字是:_G
本地变量定义在一个函数体中, 那么作用域就在函数中.
如果定义在一个控制结构中, 那么就在这个控制结构中.
如果定义在一个文件中, 那么作用域就在这个文件中.
一些lua使用中要注意的点
使用local,在代码运行前,Lua会把源码预编译成一种中间码,类似于Java的虚拟机。这种格式然后会通过C的解释器进行解释,整个过程其实就是通过一个while循环,里面有很多的switch…case语句,一个case对应一条指令来解析。 自Lua 5.0之后,Lua采用了一种类似于寄存器的虚拟机模式。Lua用栈来储存其寄存器。每一个活动的函数,Lua都会其分配一个栈,这个栈用来储存函数里的活动记录。每一个函数的栈都可以储存至多250个寄存器,因为栈的长度是用8个比特表示的。 有了这么多的寄存器,Lua的预编译器能把所有的local变量储存在其中。这就使得Lua在获取local变量时其效率十分的高。
如果你有很多非常多的很小的表需要创建时,你可以将其预先填充以避免rehash。
比如:

{true,true,true}
Lua知道这个表有三个元素,所以Lua直接创建了三个元素长度的数组。

所以,当需要创建非常多的小size的表时,应预先填充好表的大小。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值