zoukankan      html  css  js  c++  java
  • 从源码剖析Lua数据类型

    lua类型 lua示例 C类型(宏 ) C子类型(variant tags宏) 及详细说明 C数据结构
    nil(空) type(nil-->nil #define LUA_TNIL 0 //空类型  
    boolean(布尔) type(true) -->boolean #define LUA_TBOOLEAN 1 不被GC管理 int
    number(数值)

    type(3.14) -->number

    type(100) -->number

    // 若想知道number具体类型,可使用函数math.type来获取

    math.type(3.14) -->float

    math.type(100) -->integer

    注:对于其他非number类型,math.type会返回nil

    #define LUA_TNUMBER #define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4))  /* float numbers */ 3  不被GC管理

    lua_Number

    //即double

    #define LUA_TNUMINT (LUA_TNUMBER | (1 << 4))  /* integer numbers */ 19  不被GC管理

    lua_Integer

    //即__int64

    string(字符串)

    type("Hello World") -->string

    type('good') -->string

    #define LUA_TSTRING 4

    #define LUA_TSHRSTR (LUA_TSTRING | (0 << 4))  /* short strings */ 4  被GC管理

    TString

    #define LUA_TLNGSTR (LUA_TSTRING | (1 << 4))  /* long strings */ 20  被GC管理

    TString

    function(函数)

    type(print-->function

    #define LUA_TFUNCTION 6

    #define LUA_TLCL (LUA_TFUNCTION | (0 << 4))  /* Lua closure */ 6   被GC管理

    注:Lua closure即Lua function

    LClosure 由 Proto 和 UpVal 组成

    Proto描述了lua函数的函数原型

    记录着函数原型的字节码、函数引用的常量表、调试信息、参数、栈大小等信息

    UpVal保存了对upvalue的引用。它直接用一个TValue 指针引用一个upvalue值变量

    当被引用的变量还在数据栈上时,这个指针直接指向栈上的TValue,那么这个upvalue被称为开放的

    LClosure

    #define LUA_TLCF (LUA_TFUNCTION | (1 << 4))  /* light C function */ 22  不被GC管理

    LUA_TLCF即:当LUA_TCCL不包含 upvalue时,直接用lua_CFunction函数指针,不必构造Closure对象

    注:typedef int (*lua_CFunction) (lua_State *L) 

    lua_CFunction

    #define LUA_TCCL (LUA_TFUNCTION | (2 << 4))  /* C closure */ 38  被GC管理

    注:C closure即regular C function

    CClosure 由 lua_CFunction 和 TValue 组成

    C 函数可以用闭包的方式嵌入 lua,与LClosure 相比,CClosure天生就是关闭的

    因此,直接使用TValue来保存upvalue

    CClosure

    table(表) type({}) -->table #define LUA_TTABLE 5   Table
    userdata(用户数据)

    type(io.stdin) -->userdata

    注:stdin,stdout,stderr是lua提供三种预定义文件描述

    #define LUA_TLIGHTUSERDATA 2

    即轻量级用户数据(light userdata)

    只是一个指针(在c中,调用lua_pushlightuserdata将一个指针压入栈来给lua使用)

    没有元表无法得知其类型

    与数值类型一样,不被GC管理

    void*
    #define LUA_TUSERDATA 7

    即完全用户数据(full userdata)

    通常用来表示C中的结构体,可以有元表和元方法

    在c中调用lua_newuserdata创建指定大小的内存区域,被GC管理

    void*
    thread(线程)

    type(coroutine.create(function()  end)) -->thread

    #define LUA_TTHREAD 8

    lua不支持真正的多线程,实际是一个协程

    在c中调用lua_newstate来创建lua_State

    在c中调用lua_newthread创建一个线程

    被GC管理

    lua_State
       

    #define LUA_TNONE (-1) //无类型

     

     基础结构

    Value与TValue

    lua为了方便对所有的类型进行统一管理,把它们都抽象成了一个叫做Value的union结构中

    /*
    ** 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;

    从定义可以看出,主要把这些类型划分为了需要GC的类型和不需要GC的类型

    由于Value是union的结构,所以每个Value实例里同时只会有一个字段是有效的

    而为了知道具体哪个字段是有效的,也就是具体该Value是什么类型,从而有了TValue这个struct结构,主要在Value基础上wrap了一个_tt字段来标识Value的具体类型

    #define TValuefields    Value value_; int tt_
    
    
    typedef struct lua_TValue {
      TValuefields;
    } TValue;

    GCUnion、GCObject、CommonHeader

    lua把所有值按是否需要被GC,划分为了一般类型和被GC管理的类型。所有需要被GC的类型,被定义在了GCUnion里

    /*
    ** Union of all collectable objects (only for conversions)
    */
    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;  /* 线程 */
    };

    可以发现String、UserData、Closure、Table、Proto、luaState等类型都是需要被GC的,GCUnion结构和Value类似,也是同时只有一个字段是有效的

    所以我们自然而然会想到,是不是类似TValue一样,在外面给包一层type呢,但是lua实现这边并没有这样做

    而是让TString、UData这些"子类"都在各自开头定义了一个叫做CommonHeader的宏,这个宏里包含了type和一些其他字

    而每个GC类型都需要在在其struct头部定义该宏,从而可以造成一种所有GC类型都继承自一个带有CommonHeader宏的基类的假象

    /*
    ** Common type for all collectable objects
    */
    typedef struct GCObject GCObject;
    
    
    /*
    ** Common Header for all collectable objects (in macro form, to be
    ** included in other objects)
    */
    #define CommonHeader    GCObject *next; lu_byte tt; lu_byte marked
    // 注:tt,即该GC对象的具体类型
    // 注:next,指向GCObject的指针,用于GC算法内部实现链表
    // 注:marked,用于GC算法内部实现
    
    
    /*
    ** Common type has only the common header
    */
    struct GCObject {
      CommonHeader;
    };

    这样组织的好处在于lua可以把所有的GC类型的对象都视作是一个GCObject。

    再比如,lua里创建单个gcobject的函数如下

    /*
    ** create a new collectable object (with given type and size) and link
    ** it to 'allgc' list.
    */
    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;
    }

    所有的gc类型就都会调用luaC_newobj函数来创建一个GCObject实例,区别只是在于传入的type和内存size不一样而已

    该函数会根据实际传入的内存大小来开辟空间,然后填充CommonHeader部分的数据

    最后,它还会把该obj挂接到global_state结构里定义的GC列表GCObject* allgc(保存所有gc类型对象的指针)的头部,以供GC模块使用

    每个类型只用把创建出来的实例剩余内存部分的数据设置好即可,比如下面的String类型

    #define sizelstring(l)  (sizeof(union UTString) + ((l) + 1) * sizeof(char))
    
    /*
    ** Get the actual string (array of bytes) from a 'TString'.
    ** (Access to 'extra' ensures that value is really a 'TString'.)
    */
    #define getstr(ts)  
      check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString))
      
    #define gco2ts(o)  
        check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts))
      
    /*
    ** creates a new string object
    */
    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 */
      // 计算一个string实例实际内存占用大小:其实是UTString结构占用,再加上(charlength+1)个char大小
      totalsize = sizelstring(l);
      // 创建GCObject
      o = luaC_newobj(L, tag, totalsize);
      ts = gco2ts(o);
      // 填充string实例特有字段
      ts->hash = h;
      ts->extra = 0;
      // 取TString关联的char数组
      getstr(ts)[l] = '';  /* ending 0 */
      return ts;
    }

    string在创建完成以后,调用了内部的gco2ts函数,把本来指向GCObject指针强转成了指向TString的指针,然后对TString的额外元数据进行了赋值

    字符串

    考虑到性能和内存等方面,lua把String分成短字符串和长字符串两类来分开处理(注:长度大于40的为长串,反之则为短串;#define LUAI_MAXSHORTLEN 40)

    如:短串会先在全局stringtable的hashmap结构表中查询,若查询不到才会创建;而长串不查询,直接创建;两个相同的长串将会是两个副本,占用两份内存。

    主要原因是:

    ①  短串复用度会比长串要高。比如obj["id"] = 12, obj["type"] = 0,类似"id"、"type"这种短串可能会在程序很多处地方使用到,如果开辟多份就有点浪费了;而长串则很少会有重复的

    ②  长串计算哈希耗时长

    TString结构体

    /*
    ** Header for string value; string bytes follow the end of this structure
    ** (aligned according to 'UTString'; see next).
    */
    typedef struct TString {
      CommonHeader;
      lu_byte extra;  /* reserved words for short strings; "has hash" for longs */
      lu_byte shrlen;  /* length for short strings */
      unsigned int hash;
      union {
        size_t lnglen;  /* length for long strings */
        struct TString *hnext;  /* linked list for hash table */
      } u;
    } TString;

    字段含义:

    CommonHeader -- GC对象通用的CommonHeader
    extra -- 短串:用于实现保留字符串   长串:是否进行过hash的标识,为0时,表示该长串的hash还没计算过,否则表示已经计算过了
    shrlen -- 短串:长度     长串:未使用
    hash -- 字符串的hash值。短串:该hash值是在创建时就计算出来的    长串:只有真正需要它的hash值时,才会手动调用luaS_hashlongstr函数生成该值,lua内部现在只有在把长串作为table的key时,才会去计算它。
    union{lnglen, hnext} -- 短串:由于创建时会被加入到全局stringtable的链表中,所以在该结构中保存了指向下一个TString的指针;  长串:表示该长串的长度。
    注:长串和短串没有共用一个字段来表示它们的长度,主要是长串的长度可以很长,而短串最长就为40,一个byte就够用了,这边也体现了lua实现是很抠细节的,反倒是把这两个不相关的字段打包到一个union里来节约内存了。

    UTString Union

    /*
    ** Ensures that address after this type is always fully aligned.
    */
    typedef union UTString {
      L_Umaxalign dummy;  /* ensures maximum alignment for strings */
      TString tsv;
    } UTString;

    为了实现TString结构的内存对齐,lua又在其上wrap了一层UTString结构

    sizeof(L_Umaxalign)为8,保证UTString对象本身会按照8字节进行对齐

    使用luaS_newlstr创建字符串

    /*
    ** new string (with explicit length) 创建字符串
    */
    TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
      if (l <= LUAI_MAXSHORTLEN)  /* short string? */
        return internshrstr(L, str, l);  // 创建短串
      else {
        TString *ts;
        if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char))  // MAX_SIZE=0x7FFFFFFFFFFFFFFF
          luaM_toobig(L); // 长度太大了,直接报错
        ts = luaS_createlngstrobj(L, l);  // 创建长串
        memcpy(getstr(ts), str, l * sizeof(char)); // 将str的内容拷贝到ts的内存中
        return ts;
      }
    }
    
    
    /*************************************创建短串***************************************/
    /*
    ** checks whether short string exists and reuses it or creates a new one
    */
    static TString *internshrstr (lua_State *L, const char *str, size_t l) {
      TString *ts;
      global_State *g = G(L);
      unsigned int h = luaS_hash(str, l, g->seed);  // 计算Hash  为了对长度较长的字符串不逐位hash,luaS_hash函数内部也是根据长度的2次幂计算出了一个步长step,来加速hash的过程
      
      // 查找该Hash是否在全局stringtable对象g->strt中
      TString **list = &g->strt.hash[lmod(h, g->strt.size)];
      lua_assert(str != NULL);  /* otherwise 'memcmp'/'memcpy' are undefined */
      for (ts = *list; ts != NULL; ts = ts->u.hnext) {
        if (l == ts->shrlen &&
            (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
          /* found! */
          if (isdead(g, ts))  /* dead (but not collected yet)? */
            changewhite(ts);  /* resurrect it */
          // 如果在stringtable中,直接返回
          return ts;
        }
      }
      // stringtable的元素数量已经大于桶数,那么以两倍的尺寸对stringtable进行resize
      if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) {
        // luaS_resize是实际改变stringtable桶数量的函数,只会在2个地方被调用
        // 一个是这里:桶数量小于了元素数量,说明散列比较拥挤了,会对桶进行两倍的扩容
        // 即:newsize>oldsize。这个时候会先进行扩容,然后进行rehash。扩容跟到里面去调用的就是realloc函数。而rehash的代码也很简洁,就是简单的遍历每个桶,把每个桶里的元素再哈希到正确的桶里去
        // 另一个是在gc时,如果发现桶数量大于4倍的元素数量,说明散列太稀疏了,会对桶数量进行减半操作
        // 即:newsize < oldsize,顺序是倒过来的,需要先根据newsize进行rehash,然后在保证所有元素已经收缩到newsize个数的桶里以后,才能进行shrink操作,这里也是调用的realloc函数来实现
        luaS_resize(L, g->strt.size * 2);
        list = &g->strt.hash[lmod(h, g->strt.size)];  /* recompute with new size */
      }
      // 调用createstrobj函数创建TString。其中包括了内存的分配,CommonHeader的填充,TString特化字段的填充等
      ts = createstrobj(L, l, LUA_TSHRSTR, h);
      memcpy(getstr(ts), str, l * sizeof(char));
      ts->shrlen = cast_byte(l);
      ts->u.hnext = *list;
      // 更新stringtable的链表,以及对stringtable的元素数量加1
      *list = ts;
      g->strt.nuse++;
      return ts;
    }
    
    
    // 短串实现的hashmap结构体
    typedef struct stringtable {
      TString **hash; // 基于TString的hashmap,也叫做散列桶。基本结构是一个数组,每个数组里存的是相同hash值的TString的链表
      int nuse;  /* number of elements */  // 当前实际的元素数
      int size;  // 当前的桶大小
    } stringtable;
    
    
    /*************************************创建长串***************************************/
    TString *luaS_createlngstrobj (lua_State *L, size_t l) {
      TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed);
      ts->u.lnglen = l;  // 设置长串的长度变量
      return ts;
    }

    函数

    lua函数及C函数,都是一个函数闭包。函数闭包存储了函数本身以及外围作用域的局部变量(upvalue)注:env环境也是upvalue的一种

    #define ClosureHeader 
        CommonHeader; lu_byte nupvalues; GCObject *gclist
    //包含了CommonHeader的宏,表明Closure是gc类型
    //nupvalues为upvalue的个数
    //GCObject* gclist:与gc有关,将table加入到gray表中时gclist指向gray表中的下一个元素或者为空
    
    
    /*
    ** Upvalues for Lua closures  // Lua函数闭包的Upvalues
    */
    struct UpVal {
      TValue *v;  /* points to stack or to its own value */
      lu_mem refcount;  /* reference counter */  // 引用计数
      union {
        struct {  /* (when open) */
          UpVal *next;  /* linked list */
          int touched;  /* mark to avoid cycles with dead threads */
        } open;
        TValue value;  /* the value (when closed) */  // 关闭状态时的value值
      } u;
    };
    
    // C函数闭包
    typedef struct CClosure {
      ClosureHeader;
      lua_CFunction f;  // 函数指针
      TValue upvalue[1];  /* list of upvalues */  //C函数的闭包天生是关闭的,直接使用TValue来保存upvalue
    } CClosure;
    
    // Lua函数闭包
    typedef struct LClosure {
      ClosureHeader;
      struct Proto *p;  // 函数原型(Prototype)的结构
      UpVal *upvals[1];  /* list of upvalues */ //函数的upvalue指针列表,记录了该函数引用的所有upvals
    } LClosure;
    
    
    typedef union Closure {
      CClosure c;
      LClosure l;
    } Closure;

    Proto结构体

    每个函数会被编译成一个称之为原型(Proto)的结构

    原型主要包含6部分内容:函数基本信息(basic info:含参数数量、局部变量数量等信息)、字节码(bytecodes)、常量(constants)表、upvalue(闭包捕获的非局部变量)表、调试信息(debug info)、子函数原型列表(sub functions)

    // Lua虚拟机的指令集为定长(Fixed-width)指令集,每条指令占4个字节(32bits),其中操作码(OpCode)占6bits,操作数(Operand)使用剩余的26bits
    /*
    ** type for virtual-machine instructions;
    ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
    */
    #if LUAI_BITSINT >= 32
    typedef unsigned int Instruction;
    #else
    typedef unsigned long Instruction;
    #endif
    
    // 描述函数原型上的一个upvalue
    /*
    ** Description of an upvalue for function prototypes
    */
    typedef struct Upvaldesc {
      TString *name;  /* upvalue name (for debug information) */  // upvalue的名称(debug版字节码才有该信息)
      lu_byte instack;  /* whether it is in stack (register) */   // 是否在寄存器中
      lu_byte idx;  /* index of upvalue (in stack or in outer function's list) */  // upvalue在栈或外部函数列表中index
    } Upvaldesc;
    
    // 描述函数原型上的一个局部变量(debug版字节码才有该信息)
    /*
    ** Description of a local variable for function prototypes
    ** (used for debug information)
    */
    typedef struct LocVar {
      TString *varname;  // 变量名
      int startpc;  /* first point where variable is active */ // 起始指令索引
      int endpc;    /* first point where variable is dead */  // 终止指令索引
    } LocVar;
    
    // 函数原型
    /*
    ** Function Prototypes
    */
    typedef struct Proto {
      CommonHeader; // GC类型
      lu_byte numparams;  /* number of fixed parameters */ // 固定参数个数
      lu_byte is_vararg; // 是否为不定参数
      lu_byte maxstacksize;  /* number of registers needed by this function */  // 函数所需的寄存器数目
      int sizeupvalues;  /* size of 'upvalues' */  // Upvaldesc *upvalues个数
      int sizek;  /* size of 'k' */  // 常量TValue *k个数
      int sizecode;  // 指令Instruction *code个数
      int sizelineinfo; // 指令int *lineinfo行号个数 (debug版字节码才有该信息)
      int sizep;  /* size of 'p' */  // 子函数原型个数
      int sizelocvars;  // 局部变量个数
      int linedefined;  /* debug information  */  // 函数起始代码行(debug版字节码才有该信息)
      int lastlinedefined;  /* debug information  */  // 函数结束代码行(debug版字节码才有该信息)
      TValue *k;  /* constants used by the function */  // 常量表
      Instruction *code;  /* opcodes */  // 指令表
      struct Proto **p;  /* functions defined inside the function */  // 子函数原型表
      int *lineinfo;  /* map from opcodes to source lines (debug information) */  // 指令行号表(debug版字节码才有该信息)
      LocVar *locvars;  /* information about local variables (debug information) */  // 局部变量表(debug版字节码才有该信息)
      Upvaldesc *upvalues;  /* upvalue information */  // upvalue表
      struct LClosure *cache;  /* last-created closure with this prototype */  // 最近一次使用该原型创建的closure
      TString  *source;  /* used for debug information */  // 源代码文件名(debug版字节码才有该信息)
      GCObject *gclist;  // 与gc有关,将table加入到gray表中时gclist指向gray表中的下一个元素或者为空
    } Proto;

    这里的localvars和upvalues是函数原型中包含局部变量和upvalue的原始信息。在运行时,局部变量是存储在Lua栈上,upvalue索引是存储在LClosure的upvals字段中的

    CClosure结构体

    CClosure其实是lua在C侧对闭包的一个模拟。可以通过lua_pushcclosure函数来往栈上加入一个C闭包

    // 该函数会先创建一个CClosure结构,然后把提前push到栈顶的n个元素作为upvalue,将其引用存储在CClosure的upvalue数组中
    LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
      lua_lock(L);
      if (n == 0) {
        setfvalue(L->top, fn);
      }
      else {
        CClosure *cl;
        api_checknelems(L, n);
        api_check(L, n <= MAXUPVAL, "upvalue index too large");
        cl = luaF_newCclosure(L, n);
        cl->f = fn;
        L->top -= n;
        while (n--) {
          setobj2n(L, &cl->upvalue[n], L->top + n);
          /* does not need barrier because closure is white */
        }
        setclCvalue(L, L->top, cl);
      }
      api_incr_top(L);
      luaC_checkGC(L);
      lua_unlock(L);
    }

    CClosure和LClosure最大区别,在于LClosure是需要去解析lua代码来得到upvalue以及字节码等信息,在执行时需要去根据opcode来执行;

    而CClosure是一个直接的C函数,可直接执行,并且upvalue也是在创建前调用者手动push到栈上去的。

    表(table)

    table应该算是lua最灵魂的一个结构了,它有以下特点:

    容器功能:与其他语言相似,lua也内置了容器功能,也就是table。而与其他语言不同的是,lua内置容器只有table。

    table的内部结构又分为了数组和哈希表两个部分,根据不同情况来决定使用哪个部分。

    面向对象功能:与其他语言不同的时,lua并没有把面向对象的功能以语法的形式包装给开发者。

    而是保留了这样一种能力,待开发者去实现自己的面向对象,而这一保留的能力,也是封装在table里的。

    table里可以组合一个metatable,这个metatable本身也是一个table,它的字段用来描述原table的行为。

    #define TValuefields    Value value_; int tt_
    
    
    typedef union TKey {
      struct {
        TValuefields; // 主要是为了方便对value_、tt_变量的访问,不用写成tvk.value_、tvk.tt_
        int next;  /* for chaining (offset for next node) */  // 相当于当前Node的下一个Node的索引Offset(在当前Node后面,next值为正;在当前Node前面,next值为负)
      } nk;
      TValue tvk;
    } TKey;
    
    typedef struct Node {
      TValue i_val;  // value
      TKey i_key;   // key
    } Node;
    
    
    /*
    * WARNING: if you change the order of this enumeration,
    * grep "ORDER TM" and "ORDER OP"
    */
    typedef enum {
      TM_INDEX,                                           // flags = 00000001
      TM_NEWINDEX,                                         // flags = 00000010
      TM_GC,                                               // flags = 00000100
      TM_MODE,                                          // flags = 00001000
      TM_LEN,                                            // flags = 00010000
      TM_EQ,  /* last tag method with fast access */    // flags = 00100000
      TM_ADD,
      TM_SUB,
      TM_MUL,
      TM_MOD,
      TM_POW,
      TM_DIV,
      TM_IDIV,
      TM_BAND,
      TM_BOR,
      TM_BXOR,
      TM_SHL,
      TM_SHR,
      TM_UNM,
      TM_BNOT,
      TM_LT,
      TM_LE,
      TM_CONCAT,
      TM_CALL,
      TM_N        /* number of elements in the enum */
    } TMS;
    
    #define gfasttm(g,et,e) ((et) == NULL ? NULL : 
      ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))  // 先使用flags来判断,若对应的位1,就立即返回NULL
    
    #define fasttm(l,et,e)    gfasttm(G(l), et, e)
    
    
    typedef struct Table {
      CommonHeader;  // GC类型
      lu_byte flags;  /* 1<<p means tagmethod(p) is not present */ // 用来快速判断小于等于TM_EQ的tag methods是否存在:1表示不存在,0表示不确定
      lu_byte lsizenode;  /* log2 of size of 'node' array */  // 等于哈希表大小取log2(哈希表大小为2的次幂)
      unsigned int sizearray;  /* size of 'array' array */  // 数组大小(数组大小只会为2的次幂)
      TValue *array;  /* array part */  // 数组头指针(一片连续内存)
      Node *node;  // 哈希表头指针(一片连续内存)
      Node *lastfree;  /* any free position is before this position */ // 哈希表可用尾指针,可用的节点只会小于该lastfree节点
      struct Table *metatable;  // 元表
      GCObject *gclist; // 与gc有关,将table加入到gray表中时gclist指向gray表中的下一个元素或者为空
    } Table;

    array和node是两个连续空间的一维数组,array是普通的数组,成员为Tvalue,node是一个hash表存放key value键值对。

    node的key是Tkey类型,Tkey是一个联合,当没有hash冲突时Tkey是一个Tvalue,当有hash冲突时Tkey是一个struct多一个next值,指向下一个有冲突的节点,假设mp指向当前元素,则下一个元素为mp + next。

    创建表

    Table *luaH_new (lua_State *L) {
      GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
      Table *t = gco2t(o);
      t->metatable = NULL;
      t->flags = cast_byte(~0); // 255 二进制为:11111111
      t->array = NULL;
      t->sizearray = 0;
      setnodevector(L, t, 0);
      return t;
    }

    表长度

    luaH_getn函数来获取表长度

    /*
    ** Try to find a boundary in table 't'. A 'boundary' is an integer index
    ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
    */
    int luaH_getn (Table *t) {
      unsigned int j = t->sizearray;
      if (j > 0 && ttisnil(&t->array[j - 1])) { // 如果sizearray>0,且数组最后一个元素为nil
        /* there is a boundary in the array part: (binary) search for it */
        unsigned int i = 0;
        //二分查找
        while (j - i > 1) {
          unsigned int m = (i+j)/2;
          if (ttisnil(&t->array[m - 1])) j = m;  // 二分时踩到的元素为nil,则将尾部索引j向前移动到m位置
          else i = m; // 否则将头部索引i向后移动到m位置
        }
        return i;
      }
      /* else must find a boundary in hash part */
      else if (isdummy(t))  /* hash part is empty? */ // node hash表是否为空
        return j;  /* that is easy... */  // 为空表明为数组,直接返回j
      else return unbound_search(t, j);  // 为非纯数组时(即:里面含有hash node),就使用unbound_search来计算数组Length
    }

    从源码上看,table中如果有nil,会导致获取表的长度是不准确的,下面是lua5.3.4下的一些测试

    local tb1 = {0, 1, 2, nil, 4, 5, nil} -- 长度#tb1为6
    local tb2 = {0, 1, nil, 3, 4, nil}  -- 长度#tb2为2
    local tb3 = {nil, 1, nil, 3, 4, nil}  -- 长度#tb3为0
    local tb4 = {0, 1, 2, nil, 4, 5} -- 长度#tb4为6
    local tb5 = {0, 1, nil, 3, 4}  -- 长度#tb5为5
    local tb6 = {nil, 1, nil, 3, 4}  -- 长度#tb6为5
    
    local tb7 = {key1="hello"} -- 长度#tb7为0
    local tb8 = {key1="hello", nil} -- 长度#tb8为0
    local tb9 = {key1="hello", key2="world"} -- 长度#tb9为0
    local tb10 = {key1="hello", 1, nil} -- 长度#tb10为1
    local tb11 = {key1="hello", 1, 2} -- 长度#tb11为2
    local tb12 = {nil, key1="hello", 1, 2} -- 长度#tb12为3
    local tb13 = {key1="hello", 1, nil, 2} -- 长度#tb13为3
    local tb14 = {key1="hello", 1, 2, 3, nil} -- 长度#tb14为3
    local tb15 = {key1="hello", 1, nil, 2, nil} -- 长度#tb15为1

    因此,在table中不要有nil,如果一个元素要删除,直接 remove掉,不要用nil去代替

    查询

    luaH_get函数传入key来查询value,若没有查询到,则返回nil。

    如果key是int类型并且小于sizearray,那么直接返回数组对应slot(luaH_getint函数中),否则走hash表查询该key对应的slot。

    /*
    ** main search function
    */
    const TValue *luaH_get (Table *t, const TValue *key) {
      // 判断key的类型
      switch (ttype(key)) {
        case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key));// 短串
        case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); // lua_Integer整型
        case LUA_TNIL: return luaO_nilobject;
        case LUA_TNUMFLT: { // lua_Number浮点型
          lua_Integer k;
          if (luaV_tointeger(key, &k, 0)) /* index is int? */ // 将浮点型key转成整型的k
            return luaH_getint(t, k);  /* use specialized version */ // 通过k来取值
          /* else... */
        }  /* FALLTHROUGH */
        default:
          return getgeneric(t, key); // 通用取值函数(效率较低)
      }
    }
    
    
    /*
    ** search function for short strings
    */
    const TValue *luaH_getshortstr (Table *t, TString *key) {
      Node *n = hashstr(t, key);  // 使用key->hash & (2^t->lsizenode - 1)来获取node hash表中索引
      lua_assert(key->tt == LUA_TSHRSTR);
      for (;;) {  /* check whether 'key' is somewhere in the chain */
        const TValue *k = gkey(n);
        if (ttisshrstring(k) && eqshrstr(tsvalue(k), key))// Node n的键为字符串,且与key内容相同
          return gval(n);  /* that's it */  // 返回Node n的值
        else {
          int nx = gnext(n); // 获取Node n的next
          if (nx == 0)
            return luaO_nilobject;  /* not found */
          n += nx; // 获取下一个Node在node hash表中索引值n
        }
      }
    }
    
    /*
    ** search function for integers
    */
    const TValue *luaH_getint (Table *t, lua_Integer key) {
      /* (1 <= key && key <= t->sizearray) */
      if (l_castS2U(key) - 1 < t->sizearray) // key值小于t->sizearray,直接从数组中获取值
        return &t->array[key - 1];
      else {
        Node *n = hashint(t, key); // 使用key & (2^t->lsizenode - 1)来获取node hash表中索引
        for (;;) {  /* check whether 'key' is somewhere in the chain */
          if (ttisinteger(gkey(n)) && ivalue(gkey(n)) == key) // Node n的键为整型,且与key数值相等
            return gval(n);  /* that's it */ // 返回Node n的值
          else {
            int nx = gnext(n);  // 获取Node n的next
            if (nx == 0) break;
            n += nx;  // 获取下一个Node在node hash表中索引值n
          }
        }
        return luaO_nilobject;
      }
    }
    
    /*
    ** "Generic" get version. (Not that generic: not valid for integers,
    ** which may be in array part, nor for floats with integral values.)
    */
    static const TValue *getgeneric (Table *t, const TValue *key) {
      Node *n = mainposition(t, key); // 使用mainposition函数来获取key在node hash表中索引
      for (;;) {  /* check whether 'key' is somewhere in the chain */
        if (luaV_rawequalobj(gkey(n), key)) // 判断Node n的键是否与key相等
          return gval(n);  /* that's it */  // 返回Node n的值
        else {
          int nx = gnext(n);  // 获取Node n的next
          if (nx == 0)
            return luaO_nilobject;  /* not found */
          n += nx;   // 获取下一个Node在node hash表中索引值n
        }
      }
    }
    
    /*
    ** returns the 'main' position of an element in a table (that is, the index
    ** of its hash value)
    */
    static Node *mainposition (const Table *t, const TValue *key) {
      switch (ttype(key)) {
        case LUA_TNUMINT:
          return hashint(t, ivalue(key)); // 使用整型key & (2^t->lsizenode - 1)来获取node hash表中索引
        case LUA_TNUMFLT:
          return hashmod(t, l_hashfloat(fltvalue(key))); // ( l_hashfloat(浮点型key) ) % ( (2^t->lsizenode - 1) | 0x00000001 )来获取node hash表中索引
        case LUA_TSHRSTR:
          return hashstr(t, tsvalue(key)); // 使用key->hash & (2^t->lsizenode - 1)来获取node hash表中索引
        case LUA_TLNGSTR:
          return hashpow2(t, luaS_hashlongstr(tsvalue(key))); // 如果长串没有计算过hash,则调用luaS_hashlongstr来计算,然后再使用hash & (2^t->lsizenode - 1)来获取node hash表中索引
        case LUA_TBOOLEAN:
          return hashboolean(t, bvalue(key));  // 使用整型key & (2^t->lsizenode - 1)来获取node hash表中索引
        case LUA_TLIGHTUSERDATA:
          return hashpointer(t, pvalue(key)); // 使用指针(key & 0xffffffff) % ( (2^t->lsizenode - 1) | 0x00000001 )来获取node hash表中索引
        case LUA_TLCF:
          return hashpointer(t, fvalue(key)); // 使用指针(key & 0xffffffff) % ( (2^t->lsizenode - 1) | 0x00000001 )来获取node hash表中索引
        default:
          lua_assert(!ttisdeadkey(key));
          return hashpointer(t, gcvalue(key));
      }
    }
    
    #define luaV_rawequalobj(t1,t2)        luaV_equalobj(NULL,t1,t2)
    
    // 判断t1和t2数是否相等
    /*
    ** Main operation for equality of Lua values; return 't1 == t2'.
    ** L == NULL means raw equality (no metamethods)
    */
    int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) {
      const TValue *tm;
      if (ttype(t1) != ttype(t2)) {  /* not the same variant? */ // 类型不一样
        if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER) // t1 t2不是整型、浮点
          return 0;  /* only numbers can be equal with different variants */
        else {  /* two numbers with different variants */
          lua_Integer i1, i2;  /* compare them as integers */
          return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); // 如果是浮点转成整型,再进行比较
        }
      }
      /* values have same type and same variant */
      switch (ttype(t1)) {
        case LUA_TNIL: return 1; // 同为空类型,则相等
        case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2)); // 同为整型,比较数值
        case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); // 同为浮点型,比较数值
        case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2);  /* true must be 1 !! */  // 同为bool型(true:1  false:0),比较数值
        case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); // 同为指针,比较数值
        case LUA_TLCF: return fvalue(t1) == fvalue(t2); // 同为函数指针,比较数值
        case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2));// 同为短串,比较指针
        case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2));// 同为长串,逐内容比较
        case LUA_TUSERDATA: {
          if (uvalue(t1) == uvalue(t2)) return 1;
          else if (L == NULL) return 0;
          tm = fasttm(L, uvalue(t1)->metatable, TM_EQ);
          if (tm == NULL)
            tm = fasttm(L, uvalue(t2)->metatable, TM_EQ);
          break;  /* will try TM */
        }
        case LUA_TTABLE: {
          if (hvalue(t1) == hvalue(t2)) return 1;
          else if (L == NULL) return 0;
          tm = fasttm(L, hvalue(t1)->metatable, TM_EQ);
          if (tm == NULL)
            tm = fasttm(L, hvalue(t2)->metatable, TM_EQ);
          break;  /* will try TM */
        }
        default:
          return gcvalue(t1) == gcvalue(t2);
      }
      if (tm == NULL)  /* no TM? */
        return 0;  /* objects are different */
      luaT_callTM(L, tm, t1, t2, L->top, 1);  /* call TM */  // 通过TM_EQ的tag methods来判断相等性
      return !l_isfalse(L->top);// 获取luaT_callTM返回值
    }

    新增

    新增元素核心是通过luaH_newkey函数来新增key

    /*
    ** beware: when using this function you probably need to check a GC
    ** barrier and invalidate the TM cache.
    */
    TValue *luaH_set (lua_State *L, Table *t, const TValue *key) {
      const TValue *p = luaH_get(t, key); // 获取key对应value值
      if (p != luaO_nilobject) // value值不为空
        return cast(TValue *, p); // 返回该value值
      else return luaH_newkey(L, t, key);  // 否则新建一个key,并返回value值
    }
    
    /*
    ** inserts a new key into a hash table; first, check whether key's main
    ** position is free. If not, check whether colliding node is in its main
    ** position or not: if it is not, move colliding node to an empty place and
    ** put new key in its main position; otherwise (colliding node is in its main
    ** position), new key goes to an empty position.
    */
    TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
      Node *mp;
      TValue aux;
      if (ttisnil(key)) luaG_runerror(L, "table index is nil"); // key为nil,直接报错
      else if (ttisfloat(key)) { // key为浮点数
        lua_Integer k;
        if (luaV_tointeger(key, &k, 0)) {  /* does index fit in an integer? */  // 将浮点数key转成整型
          setivalue(&aux, k);
          key = &aux;  /* insert it as an integer */
        }
        else if (luai_numisnan(fltvalue(key))) // 浮点数为NaN,直接报错
          luaG_runerror(L, "table index is NaN");
      }
      mp = mainposition(t, key); // 使用mainposition函数来获取key在node hash表中索引
      if (!ttisnil(gval(mp)) || isdummy(t)) {  /* main position is taken? */ // mp节点的value有值,或者node hash表为空,需要创建新的节点
        Node *othern;
        Node *f = getfreepos(t);  /* get a free place */  // 返回空闲Node
        if (f == NULL) {  /* cannot find a free place? */  // node hash表没有空闲node
          rehash(L, t, key);  /* grow table */  // 扩容表空间
          /* whatever called 'newkey' takes care of TM cache */
          return luaH_set(L, t, key);  /* insert key into grown table */  // 将key插入到扩张后的表中
        }
        lua_assert(!isdummy(t));
        othern = mainposition(t, gkey(mp)); // 获取占用节点在node hash表中索引
        if (othern != mp) {  /* is colliding node out of its main position? */ // 如果占用节点的索引与插入key的不同,说明该节点是被“挤”到该位置来的,那么把该节点挪到freepos去,然后让newkey入住其mainposition
          /* yes; move colliding node into free position */
          while (othern + gnext(othern) != mp)  /* find previous */
            othern += gnext(othern);
          gnext(othern) = cast_int(f - othern);  /* rechain to point to 'f' */
          *f = *mp;  /* copy colliding node into free pos. (mp->next also goes) */
          if (gnext(mp) != 0) {
            gnext(f) += cast_int(mp - f);  /* correct 'next' */
            gnext(mp) = 0;  /* now 'mp' is free */
          }
          setnilvalue(gval(mp));
        }
        else {  /* colliding node is in its own main position */  // 占用的节点和newkey的哈希值相同,那么直接插入到该mainposition的next 即:从mainposition链表头部插入newkey
          /* new node will go into free position */
          if (gnext(mp) != 0)
            gnext(f) = cast_int((mp + gnext(mp)) - f);  /* chain new position */
          else lua_assert(gnext(f) == 0);
          gnext(mp) = cast_int(f - mp);
          mp = f;
        }
      }
      setnodekey(L, &mp->i_key, key); // 将key赋值给mp->i_key
      luaC_barrierback(L, t, key); // 如果表t是black(垃圾),这将其标色为gray,防止被gc掉
      lua_assert(ttisnil(gval(mp)));
      return gval(mp); // 返回mp的value值
    }
    
    
    static Node *getfreepos (Table *t) {
      if (!isdummy(t)) { // node hash表不为空
        while (t->lastfree > t->node) { // 哈希表可用尾指针大于首指针
          t->lastfree--; // 可用尾指针向前移动
          if (ttisnil(gkey(t->lastfree))) // 判断是否被使用
            return t->lastfree; // 没有被使用,则返回该指针
        }
      }
      return NULL;  /* could not find a free place */
    }

    扩展大小

    当在node hash表中找不到空闲节点时,就会调用rehash函数来扩展数组和扩展node hash表的大小。

    /*
    ** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i
    */
    static void rehash (lua_State *L, Table *t, const TValue *ek) {
      unsigned int asize;  /* optimal size for array part */ // 最终数组的大小(一定为2的次幂)
      unsigned int na;  /* number of keys in the array part */ // 最终归入数组部分的key的个数
      unsigned int nums[MAXABITS + 1]; // MAXABITS=31  它的第i个位置存储的是key在2^(i-1)~2^i区间内的数量
      int i;
      int totaluse; // 总共的key个数
      for (i = 0; i <= MAXABITS; i++) nums[i] = 0;  /* reset counts */
      na = numusearray(t, nums);  /* count keys in array part */ // 遍历当前表的array部分,按其中key的分布来更新nums数组
      totaluse = na;  /* all those keys are integer keys */
      totaluse += numusehash(t, nums, &na);  /* count keys in hash part */ // 遍历当前的hash表部分,如果其中的key为整数,na++并且更新nums数组,对于每个遍历的元素,totaluse++
      /* count extra key */
      na += countint(ek, nums); // 将ek是整型的,更新nums数组,并返回1
      totaluse++;
      // 计算optimal的array部分大小。这个函数根据整型key在2^(i-1)~2^i之间的填充率,来决定最终的array大小。
      // 一旦遇到某个子区间的填充率小于1/2,那么后续的整型key都存储到hash表中去,这一步是为了防止数组过于稀疏而浪费内存
      /* compute new size for array part */
      asize = computesizes(nums, &na);
      // 根据上一步计算出的最终数组和哈希表大小,进行resize操作。如果哈希表的尺寸有变化,会对原来哈希表中的元素进行真正的rehash
      /* resize the table to new computed sizes */
      luaH_resize(L, t, asize, totaluse - na);
    }

    迭代器

    在使用测主要是ipairs和pairs两个函数。这两个函数都会在vm内部临时创建出两个变量state和index,用于对lua表进行迭代访问,每次访问的时候,会调用luaH_next函数

    int luaH_next (lua_State *L, Table *t, StkId key) {
      // 返回key在表中的索引i
      unsigned int i = findindex(L, t, key);  /* find original element */
    
      // i < t->sizearray,表明key存放在数组中
      for (; i < t->sizearray; i++) {  /* try first array part */
        if (!ttisnil(&t->array[i])) {  /* a non-nil value? */
          setivalue(key, i + 1);
          setobj2s(L, key+1, &t->array[i]);
          return 1;
        }
      }
      // 否则key在node hash表中
      for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) {  /* hash part */
        if (!ttisnil(gval(gnode(t, i)))) {  /* a non-nil value? */
          setobj2s(L, key, gkey(gnode(t, i)));
          setobj2s(L, key+1, gval(gnode(t, i)));
          return 1;
        }
      }
      return 0;  /* no more elements */
    }

    userdata(用户数据)

    Lua和C交互所使用的的自定义数据分为userdata和lightuserdata两个子类,这两者的根本区别在于内存生命周期的管理者不同。

    userdata的内存在Lua栈上分配。用户使用userdata时,通过调用lua_newuserdata(lua_State* L, size_t nBytes)分配指定大小的内存块,类似于malloc,但不需要自行调用free(),该内存由Lua的gc机制进行回收。

    lightuserdata只是通过lua_pushlightuserdata(ua_state* L, void* p)将C对象指针交给Lua的对象持有,lightuserdata所使用的内存的分配和回收,需要用户自行管理,Lua并不会帮忙回收。

    尤其需要注意的是,Lua中lightuserdata的对象生命周期与绑定C对象的生命周期息息相关,因此C对象释放时,Lua中的lightuserdata的释放也需要用户关心处理,否则会出现野指针问题。

    Udata结构体与UUdata Union

    /*
    ** Header for userdata; memory area follows the end of this structure
    ** (aligned according to 'UUdata'; see next).
    */
    typedef struct Udata {
      CommonHeader;
      lu_byte ttuv_;  /* user value's tag */  // Udata存放的类型
      struct Table *metatable; // userdata的元表,和table的元表一样的
      size_t len;  /* number of bytes */ // 使用userdata的时候绑定对象申请的空间大小
      union Value user_;  /* user value */  // Udata存放的value
    } Udata;
    
    
    // 为了实现Udata结构的内存对齐,lua又在其上wrap了一层UUdata结构
    /*
    ** Ensures that address after this type is always fully aligned.
    */
    typedef union UUdata {
      // sizeof(L_Umaxalign)为8,保证UUdata对象本身会按照8字节进行对齐
      L_Umaxalign dummy;  /* ensures maximum alignment for 'local' udata */
      Udata uv;
    } UUdata;

    使用luaS_newudata创建userdata

    Udata *luaS_newudata (lua_State *L, size_t s) {
      Udata *u;
      GCObject *o;
      if (s > MAX_SIZE - sizeof(Udata))
        luaM_toobig(L);
      o = luaC_newobj(L, LUA_TUSERDATA, sizeludata(s));
      u = gco2u(o);
      u->len = s;
      u->metatable = NULL;
      setuservalue(L, u, luaO_nilobject);
      return u;
    }

    thread(线程)

    从Lua的使用者的角度看,global_State是不可见的。我们无法用公开的API取到它的指针,也不需要引用它。但分析Lua的实现就不能绕开这个部分。
    global_State里面有对主线程的引用,有注册表管理所有全局数据,有全局字符串表,有内存管理函数,
    有GC需要的把所有对象串联起来的相关信息,以及一切Lua在工作时需要的工作内存。

    typedef struct global_State {
      lua_Alloc frealloc;  /* function to reallocate memory */
      void *ud;         /* auxiliary data to 'frealloc' */
      l_mem totalbytes;  /* number of bytes currently allocated - GCdebt */
      l_mem GCdebt;  /* bytes allocated not yet compensated by the collector */
      lu_mem GCmemtrav;  /* memory traversed by the GC */
      lu_mem GCestimate;  /* an estimate of the non-garbage memory in use */
      stringtable strt;  /* hash table for strings */
      TValue l_registry;
      unsigned int seed;  /* randomized seed for hashes */
      lu_byte currentwhite;
      lu_byte gcstate;  /* state of garbage collector */
      lu_byte gckind;  /* kind of GC running */
      lu_byte gcrunning;  /* true if GC is running */
      GCObject *allgc;  /* list of all collectable objects */
      GCObject **sweepgc;  /* current position of sweep in list */
      GCObject *finobj;  /* list of collectable objects with finalizers */
      GCObject *gray;  /* list of gray objects */
      GCObject *grayagain;  /* list of objects to be traversed atomically */
      GCObject *weak;  /* list of tables with weak values */
      GCObject *ephemeron;  /* list of ephemeron tables (weak keys) */
      GCObject *allweak;  /* list of all-weak tables */
      GCObject *tobefnz;  /* list of userdata to be GC */
      GCObject *fixedgc;  /* list of objects not to be collected */
      struct lua_State *twups;  /* list of threads with open upvalues */
      unsigned int gcfinnum;  /* number of finalizers to call in each GC step */
      int gcpause;  /* size of pause between successive GCs */
      int gcstepmul;  /* GC 'granularity' */
      lua_CFunction panic;  /* to be called in unprotected errors */
      struct lua_State *mainthread;
      const lua_Number *version;  /* pointer to version number */
      TString *memerrmsg;  /* memory-error message */
      TString *tmname[TM_N];  /* array with tag-method names */
      struct Table *mt[LUA_NUMTAGS];  /* metatables for basic types */
      TString *strcache[STRCACHE_N][STRCACHE_M];  /* cache for strings in API */
    } global_State;
    
    
    /*
    ** 'per thread' state
    */
    struct lua_State {
      CommonHeader;
      unsigned short nci;  /* number of items in 'ci' list */
      lu_byte status;
      StkId top;  /* first free slot in the stack */
      global_State *l_G;
      CallInfo *ci;  /* call info for current function */
      const Instruction *oldpc;  /* last pc traced */
      StkId stack_last;  /* last free slot in the stack */
      StkId stack;  /* stack base */
      UpVal *openupval;  /* list of open upvalues in this stack */
      GCObject *gclist;
      struct lua_State *twups;  /* list of threads with open upvalues */
      struct lua_longjmp *errorJmp;  /* current error recover point */
      CallInfo base_ci;  /* CallInfo for first level (C calling Lua) */
      volatile lua_Hook hook;
      ptrdiff_t errfunc;  /* current error handling function (stack index) */
      int stacksize;
      int basehookcount;
      int hookcount;
      unsigned short nny;  /* number of non-yieldable calls in stack */
      unsigned short nCcalls;  /* number of nested C calls */
      l_signalT hookmask;
      lu_byte allowhook;
    };

    创建一个lua_State

    lua_newstate中初始化了主线程的数据栈、初始化注册表、给出一个基本的字符串池、初始化元表用的字符串、初始化词法分析用的token串5、初始化内存错误信息。

    Lua_State是暴露给用户的数据类型。从名字上看,它想表示一个Lua程序的执行状态,在官方文档中,它指代Lua的一个线程。

    每个线程拥有独立的数据栈以及函数调用链,还有独立的调试钩子和错误处理设施。所以我们不应当简单的把Lua_State看成一个静态的数据集,它是一组Lua程序的执行状态机。

    所有的Lua C API都是围绕这个状态机,改变其状态的:或把数据压入堆栈,或取出,或执行栈顶的函数,或继续上次被中断的执行过程。

    同一Lua虚拟机中的所有执行线程,共享了一块全局数据global_State。

    LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
      int i;
      lua_State *L;
      global_State *g;
      LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
      if (l == NULL) return NULL;
      L = &l->l.l;
      g = &l->g;
      L->next = NULL;
      L->tt = LUA_TTHREAD;
      g->currentwhite = bitmask(WHITE0BIT);
      L->marked = luaC_white(g);
      preinit_thread(L, g);
      g->frealloc = f;
      g->ud = ud;
      g->mainthread = L;
      g->seed = makeseed(L);
      g->gcrunning = 0;  /* no GC while building state */
      g->GCestimate = 0;
      g->strt.size = g->strt.nuse = 0;
      g->strt.hash = NULL;
      setnilvalue(&g->l_registry);
      g->panic = NULL;
      g->version = NULL;
      g->gcstate = GCSpause;
      g->gckind = KGC_NORMAL;
      g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL;
      g->sweepgc = NULL;
      g->gray = g->grayagain = NULL;
      g->weak = g->ephemeron = g->allweak = NULL;
      g->twups = NULL;
      g->totalbytes = sizeof(LG);
      g->GCdebt = 0;
      g->gcfinnum = 0;
      g->gcpause = LUAI_GCPAUSE;
      g->gcstepmul = LUAI_GCMUL;
      for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;
      if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {
        /* memory allocation error: free partial state */
        close_state(L);
        L = NULL;
      }
      return L;
    }

    把数据栈和调用栈合起来就构成了Lua中的线程。在同一个Lua虚拟机中的不同线程因为共享了global_State而很难做到真正意义上的并发。

    它也绝非操作系统意义上的线程,但在行为上很相似。用户可以resume一个线程,线程可以被yield打断。Lua的执行过程就是围绕线程进行的。

    创建一个thread

    LUA_API lua_State *lua_newthread (lua_State *L) {
      global_State *g = G(L);
      lua_State *L1;
      lua_lock(L);
      luaC_checkGC(L);
      /* create new thread */
      L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l;
      L1->marked = luaC_white(g);
      L1->tt = LUA_TTHREAD;
      /* link it on list 'allgc' */
      L1->next = g->allgc;
      g->allgc = obj2gco(L1);
      /* anchor it on L stack */
      setthvalue(L, L->top, L1);
      api_incr_top(L);
      preinit_thread(L1, g);
      L1->hookmask = L->hookmask;
      L1->basehookcount = L->basehookcount;
      L1->hook = L->hook;
      resethookcount(L1);
      /* initialize L1 extra space */
      memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread),
             LUA_EXTRASPACE);
      luai_userstatethread(L, L1);
      stack_init(L1, L);  /* init stack */
      lua_unlock(L);
      return L1;
    } 

    参考

    Lua设计与实现--数据类型篇

    Lua设计与实现--字符串篇

    Lua设计与实现--函数篇

    Lua设计与实现--Table篇

    [lua source code] object system

    温故而知新

    lua字符串

    lua5.3.4源码 

  • 相关阅读:
    javascript中map的用法
    洛谷P1057传球游戏-题解
    洛谷P1049装箱问题-题解
    洛谷P1048采药-题解
    洛谷P1044栈-题解
    洛谷P1040加分二叉树-题解
    洛谷P1005矩阵取数游戏-题解
    洛谷P1004方格取数-题解
    洛谷P1002过河卒-题解
    搜索
  • 原文地址:https://www.cnblogs.com/kekec/p/13062758.html
Copyright © 2011-2022 走看看