lua设计与实现_[Lua虚拟机解析系列]01 Lua中的数据类型

a726d6bd9e2bcd91042e833f31698b91.png

**:

  • 本文根据
lichuang/Lua-Source-Internal​github.com
ede7dac8179135cb368d751cfd747326.png

修改而成,原文参考Lua版本为5.1.4,本文参考Lua版本为5.3.5

  • 本文 Github 地址:
yangruihan/Lua-Source-Internal​github.com
8342cb29d860f34cc748e4c71895650b.png

Lua中的数据类型

在 Lua 中,分以下几种数据类型:

/**
 * Lua 基本类型
 * (lua.h)
 */

#define LUA_TNONE       (-1)

#define LUA_TNIL        0
#define LUA_TBOOLEAN        1
#define LUA_TLIGHTUSERDATA  2
#define LUA_TNUMBER     3
#define LUA_TSTRING     4
#define LUA_TTABLE      5
#define LUA_TFUNCTION       6
#define LUA_TUSERDATA       7
#define LUA_TTHREAD     8

那么 Lua 中是怎么样表示一个数据呢,可以看一下源代码

/**
 * Union of all Lua values
 * (lobject.h)
 */
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;

结合上面的类型,我们可以得到一张对应表

0a3a941344185fb73d69979770607bf7.png

其中大部分类型很容易理解,比如LUA_TNUMBER就表示的数值类型,LUA_TBOOLEAN表示的是布尔类型,不过这里有两个比较特殊的类型:LUA_TLIGHTUSERDATALUA_TUSERDATA一样,对应的都是void*指针,区别在于,LUA_TLIGHTUSERDATA的分配释放是由 Lua 外部的使用者来完成,而LUA_TUSERDATA则是通过 Lua 内部来完成的,换言之,前者不需要 Lua 去关心它的生存期,由使用者自己去关注,后者则反之。

不过这些数据到底是什么类型呢。于是 Lua 代码中又有了一个TValuefields,将Value和类型结合在一起:

/**
 * value_ 值内容
 * tt_ 类型
 * (lobject.h)
 */
#define TValuefields    Value value_; int tt_

其中tt_表示的就是数据的类型,但它并不是直接用数值进行表示,而是用不同的位代表不同的含义,从lobject.h源代码中可知,类型tt_有如下规则:

  • 0-3 位:实际的类型,对应上文(lua.h)中的LUA_T*字段
  • 4-5 位:为变体类型位,对于不同的类型,变体有其不同的含义
    • function:
      • 0 Lua function
      • 1 Light C function
      • 2 Regular C function
    • string:
      • 0 short string
      • 1 long string
    • number:
      • 0 float number
      • 1 integer number
  • 6 位:表示类型是否可以可以被 gc

Lua 在lobject.h中提供了一系列宏来方便类型检查、类型转换。

回到Value这个联合体,可以看到,基础类型非常简单,直接用其中的字段进行表示,接下来主要看一下需要进行 gc 的数据

Lua 内部用一个宏iscollectable,表示哪些数据类型需要进行 gc 操作的,下面是相关代码:

/* (lobject.h) */

/* Bit mark for collectable types */
#define BIT_ISCOLLECTABLE   (1 << 6)

/* TValue 的类型 */
#define rttype(o)   ((o)->tt_)

// 判断类型是否为可GC类型
#define iscollectable(o)    (rttype(o) & BIT_ISCOLLECTABLE)

可以看到,tt_的第7位用来表示该数据是否可回收

那么,对于这些需要进行 gc 操作的数据类型,在 Lua 中是如何表示的呢?

这些需要 gc 的数据类型,都会有一个CommonHeader的成员,并且这个成员在结构体定义的最开始部分,如:

/**
 * Lua Table 实现
 * (lobject.h)
 */
typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
  lu_byte lsizenode;  /* log2 of size of 'node' array */
  unsigned int sizearray;  /* size of 'array' array */
  TValue *array;  /* array part */
  Node *node;
  Node *lastfree;  /* any free position is before this position */
  struct Table *metatable;
  GCObject *gclist;
} Table;

其中CommonHeader的定义如下:

/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
** (lobject.h)
*/
#define CommonHeader    GCObject *next; lu_byte tt; lu_byte marked

同时,还有一个名为GCObject的结构体,其中的成员只有CommonHeader:

/*
** Common type has only the common header
** (lobject.h)
*/
struct GCObject {
  CommonHeader;
};

在 Lua 中还使用了一个GCUnion的 union 将所有可 gc 类型囊括了进来:

/*
** Union of all collectable objects (only for conversions)
** (lstate.h)
*/
union GCUnion {
  GCObject gc;  /* common header */
  struct TString ts;
  struct Udata u;
  union Closure cl;
  struct Table h;
  struct Proto p;
  struct lua_State th;  /* thread */
};

我们整理一下前面提到的这么几个结构体,可以得到这样的结论:

  1. 任何需要 gc 的 Lua 数据类型,必然以CommonHeader做为该结构体定义的最开始部分。如果熟悉C++类实现原理的人,可以将CommonHeader这个成员理解为一个基类的所有成员,而其他需要 gc 的数据类型均从这个基类中继承下来,所以它们的结构体定义开始部分都是这个成员。
  2. GCUnion这个 union,将所有需要 gc 的数据类型全部囊括其中,这样在定位和查找不同类型的数据时就来的方便多了,而如果只想要它们的 gc 部分,可以通过Valuegc字段,如:
/* (lobject.h) */
#define val_(o)     ((o)->value_)

#define gcvalue(o)  check_exp(iscollectable(o), val_(o).gc)

以上提到的结构关系如下图:

1444db93d9d82be74e538e0ce3fe1bc4.png

总结

  • GC 类型中有CommonHeader,用来存放所有数据类型都通用的字段
  • TValue作为统一表示所有数据的数据结构,内部使用了联合体Value将所有数据都包起来

在具体代码中,TValue用于统一地表示数据,而一旦知道了具体的类型,就需要使用具体的类型转换逻辑,将其转换成对应的类型

接着,创建一个GCObject逻辑如下:

/*
** create a new collectable object (with given type and size) and link
** it to 'allgc' list.
** (lgc.c)
*/
GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) {
  global_State *g = G(L);
  GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz));
  o->marked = luaC_white(g);
  o->tt = tt;
  o->next = g->allgc;
  g->allgc = o;
  return o;
}

TStringTable这种子类型,都会调用这个接口来创建一个GCObject,区别只是传入的type(tt)和内存size(sz)不一样。这个公用函数也会初始化掉CommonHeader部分的数据,每个类型只需要把创建好实例剩余部分的数据设置好即可,比如下面举String类型的例子:

/*
** creates a new string object
** (lstring.c)
*/
static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) {
  TString *ts;
  GCObject *o;
  size_t totalsize;  /* total size of TString object */
  totalsize = sizelstring(l);
  o = luaC_newobj(L, tag, totalsize);
  ts = gco2ts(o);
  ts->hash = h;
  ts->extra = 0;
  getstr(ts)[l] = '0';  /* ending 0 */
  return ts;
}

引用关系图如下:

336b7ae03670fba118dcb1f802684d29.png

END

下一章:[Lua虚拟机解析系列]02 Lua字符串类型,传送门

参考文章:

  • 《Lua设计与实现》第二章及其开源文档
lichuang/Lua-Source-Internal​github.com
ede7dac8179135cb368d751cfd747326.png
  • 知乎文章:
张德嘉:Lua设计与实现--数据类型篇​zhuanlan.zhihu.com
dac2d8bae4150ca6c7be3c0e0a85e27e.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值