• 从源码剖析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源码 

  • 相关阅读:
    6-Python爬虫-分布式爬虫/Redis
    ES 查询时 排序报错(fielddata is disabled on text fileds by default ... )解决方法
    Intellij Idea webstorm 激活
    Intellij Idea 配置jdk
    java 获取(格式化)日期格式
    js 跳转 XSS漏洞 预防
    CSS去掉背景颜色
    js对象无法当成参数传递 解决方法
    Elasticsearch java api
    java多条件查询SQL语句拼接的小技巧
  • 原文地址:https://www.cnblogs.com/kekec/p/13062758.html
Copyright © 2020-2023  润新知