فهرست منبع

feat: 添加 lua 源码结构体解析

Nicetry12138 3 ماه پیش
والد
کامیت
7264208891
3فایلهای تغییر یافته به همراه513 افزوده شده و 1 حذف شده
  1. 501 0
      README.md
  2. BIN
      image/001.png
  3. 12 1
      src/lua-5.4.8/test/test.lua

+ 501 - 0
README.md

@@ -558,4 +558,505 @@ end --> 2
 
 ## Lua 源码
 
+从 C++ 中启动 Lua 虚拟机并指定对应的 lua 文件的基本流程如下
+
+```cpp
+#include <iostream>
+#include <lua.hpp>
+
+int main() {
+	lua_State* L = luaL_newstate(); // 使用luaL_newstate()代替lua_open()
+
+	// 加载标准库
+	luaL_openlibs(L);
+
+	// 执行Lua文件
+	luaL_dofile(L, "test/test.lua");
+
+	lua_close(L);
+	return 0;
+}
+```
+
+
+1. 通过 `luaL_newstate` 创建全局状态对象 `global_State` 和主线程 `lua_State`、初始化堆栈、字符串表、GC、调用接口元数、创建注册表等
+2. 通过 `luaL_opemlibs` 遍历 `linit.c`` 中的库列表,依次执行 `luaL_requiref` 并将函数注册到库表中,将库表复制到全局 `_G[name]`
+3. 调用 `luaL_doFile` 来执行指定 lua 文件
+
+### 常用结构体
+
+#### global_State
+
+`global_State` 是一个 Lua 虚拟机实例的 **全局运行时** ,被所有线程 `lua_State` 共享;负责内存分配、字符串驻留、GC(增量/分代)、类型默认元表、注册表、panic/warn、主线程与打开 `upvalue` 的线程链等
+
+绝大多数字段仅内部使用;开发者通过公开 API(`lua_newstate`、`lua_gc`、`lua_setwarnf`、`lua_atpanic`、`LUA_REGISTRYINDEX`、`debug.*` 等)间接影响或观察其效果
+
+| 属性名 | 作用 |
+| --- | --- |
+| `frealloc/ud` | 自定义分配器及其上下文;影响所有对象分配/释放 |
+| `strt` | 字符串驻留哈希表 |
+| `l_registry` | 注册表(全局)表,跨库共享状态 |
+| `nilvalue` | 单例 nil 值 |
+| `seed` | 哈希随机种子,抵御恶意碰撞 |
+| `twups` | 含“打开的 upvalue”的线程链。示例:主线程创建闭包,协程使用该闭包,线程将被挂到 twups,确保 upvalue 生命周期正确 |
+| `panic` | 未受保护错误时的回调(终止前调用)。例如 `lua_atpanic`` 注册,避免直接触发以免中止进程 |
+| `mainthread` | 主线程指针 |
+| `memerrmsg` | 分配内存失败时的错误消息字符串 |
+| `tmname[TM_N]` | 内置元方法名称表,用于将枚举 TM_* 映射到字符串名 |
+| `mt[LUA_NUMTYPES]` | 各基础类型的默认元表指针数组(nil/boolean/lightuserdata/number/string/table/function/userdata/thread) |
+| `strcache[STRCACHE_N][STRCACHE_M]` | API 层的短字符串缓存(最近/热路径缓存),加速 lua_pushstring 等重复驻留的查找 |
+| `warnf/ud_warn` | 警告回调及其上下文,lua_setwarnf(L, mywarn, ud) 注册;在解析/运行期间调用 lua_warning(L, "message", tocont) 触发非致命提示(如弃用特性或潜在问题) |
+
+除了上述这些属性之外,剩下的属性几乎都是跟 GC 相关的
+
+| GC 相关的属性 | 作用 |
+| --- | --- |
+| `currentwhite/gcstate/gckind/gcstopem/gcstp/gcemergency/gcpause/gcstepmul/gcstepsize` | GC 三色标记当前白色集合、GC 状态机阶段、GC 模式(增量/分代)、应急收集阻断、是否暂停 GC、是否处于应急、暂停参数、步进速度与粒度 |
+| `allgc` | 所有可回收对象总链 |
+| `sweepgc` | 当前清扫指针 |
+| `finobj/tobefnz` | 带 __gc 的对象与待执行 finalizer 的队列。示例:创建带 __gc 的 userdata,然后 lua_gc(L, LUA_GCCOLLECT) |
+| `gray/grayagain` | 增量/原子阶段需遍历的“灰色”对象 |
+| `weak/ephemeron/allweak` | 弱表(弱值/弱键/全弱)集合。示例:__mode='v'/'k' 的表在 GC 后被清理 |
+| `fixedgc` | 固定对象(如保留字符串)不被回收 |
+| `survival/old1/reallyold/firstold1/finobjsur/finobjold1/finobjrold` | 分代 GC 的分龄链表与对应 finalizer 队列 |
+
+#### lua_State
+
+`lua_State` 表示单个 **线程/协程** 的运行时对象,持有栈、调用帧、错误处理、钩子等
+
+> C API 里的 L 指针就是它
+
+| 属性 | 数据类型 | 作用 | 例子 |
+| --- | --- | --- | --- |
+| `CommonHeader` |  | GC 标头(类型、标志、颜色)。仅内部使用 | 通过 lua_gc 展示可达性与回收 |
+| `status` | lu_byte | 线程状态 | lua_status(L) 可得;协程 yield 后为 LUA_YIELD,可用于协程切换 |
+| `allowhook` | lu_byte | 是否允许触发 hook(内部门闸)。不可直接访问 | 通过 lua_sethook 验证在某些阶段不触发 |
+| `nci` | unsigned | CallInfo 节点数量(调用深度)。不可直接读取 | 用深度递归观察错误“C stack overflow” |
+| `top` | StkIdRel | 栈顶第一个空槽 | push/pop 后记录可见索引与有效值边界 |
+| `l_G` | global_State | 指向 global_State。不可直接访问 | 通过 LUA_REGISTRYINDEX 操作注册表以体现全局状态 |
+| `ci` | CallInfo | 当前调用帧信息。不可直接访问 | 用 debug.getinfo 观察当前函数、栈层级 |
+| `stack_last` | StkIdRel | 栈尾(最后元素 + 1) | lua_checkstack 扩栈与失败处理 |
+| `stack` | StkIdRel | 栈基址(当前帧底) | 正负索引读取、验证越界行为 |
+| `openupval` | UpVal | 打开的 upvalue 链表 | 闭包捕获局部变量并跨协程使用,展示“打开的 upvalue” |
+| `tbclist` | StkIdRel | 待关闭变量链表 | Lua 5.4 的 变量与 __close 元方法 |
+| `gclist` | GCObject | 等待处理的 GC 对象链 | userdata 带 __gc,调用 lua_gc(L, LUA_GCCOLLECT) 观察析构 |
+| `twups` | lua_State* | 持有打开 upvalue 的线程链 | 主线程与协程共享 upvalue,GC 阶段仍保持活性 |
+| `errorJmp` | lua_longjmp* | C 层异常恢复点 | lua_pcall 捕获 lua_error |
+| `base_ci` | CallInfo | 首层 CallInfo(C 调 Lua 的入口帧)。不可直接访问 | 从 C 执行 Lua chunk 观察栈基变化 |
+| `hook` | lua_Hook | 当前调试钩子函数指针 | lua_sethook 注册并记录事件 |
+| `errfunc` | ptrdiff_t | 当前错误处理函数(栈索引) | lua_pcall 带消息处理器 | 
+| `nCcalls` | l_uint32 | 嵌套的(不可让步|C)调用计数 | C 函数递归并观察是否阻止 yield |
+| `oldpc` | int | 最后一次跟踪的 PC(内部) | 指令/行号钩子中观察行为 |
+| `basehookcount/hookcount/hookmask` | intl_signalT | 钩子计数与掩码 | 设置 LUA_MASKCOUNT/LUA_MASKLINE/LUA_MASKCALL 并记录触发频率 |
+
+#### CallInfo
+
+```cpp
+static int str_find (lua_State *L) {
+  return str_find_aux(L, 1);
+}
+
+static int str_find_aux (lua_State *L, int find) {
+  size_t ls, lp;
+  const char *s = luaL_checklstring(L, 1, &ls);
+  const char *p = luaL_checklstring(L, 2, &lp);
+  // some code else .....
+}
+
+LUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) {
+  const char *s = lua_tolstring(L, arg, len);
+  if (l_unlikely(!s)) tag_error(L, arg, LUA_TSTRING);
+  return s;
+}
+
+LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) {
+  TValue *o;
+  lua_lock(L);
+  o = index2value(L, idx);
+  // some code else ...
+}
+
+static TValue *index2value (lua_State *L, int idx) {
+  CallInfo *ci = L->ci;
+  if (idx > 0) {
+    StkId o = ci->func.p + idx;
+    api_check(L, idx <= ci->top.p - (ci->func.p + 1), "unacceptable index");
+    if (o >= L->top.p) return &G(L)->nilvalue;
+    else return s2v(o);
+  }
+
+  // some code else ...
+}
+```
+
+以 `str_find` 为例,该函数是用来查找匹配字符串的,在 lua 中原型是 `string.find(s, p [, init [, plain]])`
+
+- s,subject, 表示要搜索的字符串
+- p,pattern,模式/子串
+- init, 起始位置,默认为 1,允许使用负数表示相对尾部
+- plain,布尔值,true 表示禁用模式语法,做字面量匹配
+
+从 `str_find_aux` 函数中可以发现
+
+- `const char *s = luaL_checklstring(L, 1, &ls)` 这里的 s 就是 subject,即要搜索的字符串,因为它是函数参数栈中的第一个,所以第二个参数传入 1
+- `const char *p = luaL_checklstring(L, 2, &lp)` 这里的 p 就是 pattern,即要匹配的子串,因为它是函数参数栈中的第二个,所以第三个参数传入 2
+
+`CallInfo` 是 Lua 虚拟机对 **一次函数调用** 的栈帧描述,链成双向链表形成调用栈;每个活跃函数(Lua/C)对应一个 CallInfo
+
+> 这里 **帧** 指的就是一次函数调用的活动记录,说白了就是一次函数调用
+
+`CallInfo` 限制本帧可用栈顶、记录当前执行位置、变参信息、yield/继续执行的上下文、受保护调用的错误处理器、返回值期望、调试钩子传输的值范围,以及各种状态位(Lua/C、tail call、正在关闭 tbc 变量等)
+
+| CallInfo 的属性 | 作用 |
+| --- | --- |
+| func | 本帧函数在栈中的相对索引(StkIdRel);用于定位函数闭包/closure |
+| top | 本帧允许使用的栈顶(StkIdRel);避免越界干扰其他帧 |
+| *previous, *next | 调用链前后指针;形成双向链表。可快速切换当前活动帧、回退返回 |
+| u.l.savedpc | (仅 lua 函数) 当前字节码指令地址,用于恢复继续执行 |
+| u.l.trap | (仅 lua 函数) 线路/技术 跟踪标志,用于在执行到下一条指令或计数阈值时强制执行调式狗子,比如 `debug.sethook(h, "l")` |
+| u.l.nextraargs | (仅 lua 函数) 变参函数的 **额外参数** 数量,比如 `function f(a, ...) end` 此时调用 `f(1, 2, 3, 4)` 那么 `nextraargs` 值为 3 |
+| u.c.k | (仅 C 函数)继续函数(continuation),用于可让出/可恢复的 C 调用 |
+| u.c.old_errfunc | (仅 C 函数)受保护调用中的旧错误处理索引,用于在 pcall 链嵌套时恢复前一个错误处理器 |
+| u.c.ctx | 继续函数的上下文值(lua_KContext,C 指针或整数) |
+| u2 | 不太懂 |
+| nresults | 本次调用期望返回值的数量, VM 用它在返回时裁剪或补齐结果,比如 `lua_call(L, f, 1)` 值保留一个返回值,其余丢弃 |
+| callstatus | 帧状态位的集合(位标志),比如 `CIST_C` 表示这是 C函数、`CIST_TAIL` 表示尾调用 等 |
+
+> 属性 u 是一个 union,其包含 `struct l` 表示 lua 函数调用 和 `struct c` 表示 c 函数调用 两个结构体
+
+lua 线程的栈为一段连续的槽位 (`TValue` 数组),所有帧共享同一个物理栈。每个调用帧用 CallInfo 限定自身可用的槽位范围
+
+`CallInfo.top` 表示本帧可使用的最大允许槽位上限,用于限制本帧内的堆栈/寄存器访问
+
+其实在 `lua_State` 中也有一个 `top`,其表示当前线程 **已用栈** 的下一个空位
+
+那么此时回到 `index2value` 函数
+
+> `index2value` 这里 2 就是 two,与 to 同音,译为得到 index 对应的 value
+
+```cpp
+static TValue *index2value (lua_State *L, int idx) {
+  CallInfo *ci = L->ci;
+  if (idx > 0) {
+    StkId o = ci->func.p + idx;
+    api_check(L, idx <= ci->top.p - (ci->func.p + 1), "unacceptable index");
+    if (o >= L->top.p) return &G(L)->nilvalue;
+    else return s2v(o);
+  }
+  // some code else 
+}
+```
+
+从 `ci->func.p + idx` 可以看出,`CallInfo.func.p` 表示当前帧函数在栈中的相对索引,那么函数的第一个参数就是 `CallInfo.func.p + 1`
+
+> 这里的 `CallInfo.func.p` 是一个 `StackValue *`,对其 `+1` 表示指针向后移动 1 个 `StackValue`,即槽位的下一个
+
+#### Value、TValue、StackValue
+
+##### Value 和 TValue
+
+```cpp
+typedef union Value {
+  struct GCObject *gc;    /* 指向可回收对象(字符串、表、Lua/C 闭包、完整 userdata、线程等) */
+  void *p;                /* 轻量 userdata 的裸指针(不受 GC 管理) */
+  lua_CFunction f;        /* 轻量 C 函数指针(lua_CFunction) */
+  lua_Integer i;          /* 整型数(lua_Integer) */
+  lua_Number n;           /* 浮点数(lua_Number) */
+  lu_byte ub;             /* 未用,占位避免未初始化告警 */
+} Value;
+```
+
+`Value` 是 `Lua` 内部 **值载体** 的联合体,用于存放具体数据的位表示,但是其真正的类型是什么是由 `TValue` 的 `tt_` 标识符来决定
+
+```cpp
+#define TValuefields	Value value_; lu_byte tt_
+
+typedef struct TValue {
+  TValuefields;
+} TValue;
+```
+
+从上面代码可知 `TValue` 包含两个属性,一个是 `value_` 存储具体的值,一个是 `tt_` 表示值类型
+
+```cpp
+// ttisstring(o) 判断 TValue o 是否是 string
+#define ttisstring(o)		checktype((o), LUA_TSTRING)
+#define checktype(o,t)		(ttype(o) == (t))
+#define ttype(o)	(novariant(rawtt(o)))
+#define novariant(t)	((t) & 0x0F)
+#define rawtt(o)	((o)->tt_)
+```
+
+上述代码从上往下,本质上就是判断 `o->tt_` 与 `LUA_STRING` 是否相等,以此来判断 `o` 是否是 `string`
+
+| 属性宏 | 值 | 对应类型 |
+| --- | --- | --- |
+| LUA_TNONE | 0 | 无类型,用于栈索引无效或无返回值位置,非值本身 |
+| LUA_TNIL | 1 | nil,无值 |
+| LUA_TBOOLEAN | 2 | 布尔值 |
+| LUA_TLIGHTUSERDATA	 | 3 | 轻量 `userdata`;裸 c 指针,不受 GC 管理,对应 `Value.p` |
+| LUA_TNUMBER | 4 | 数值的外部类型,内部有整数与浮点,对应 `Value.i` 和 `Value.n` |
+| LUA_TSTRING | 5 | 字符串,GC管理对象,载荷为 Value.gc 指向 `TString` |
+| LUA_TTABLE | 6 | 表,GC管理对象,载荷为 Value.gc 指向 `Table` |
+| LUA_TFUNCTION | 7 | 函数的外部类型,内部有三种 lua 闭包 Value.gc 指向 LClosure; C 闭包 Value.gc 指向 CClosure;轻量 C 函数 Value.f (非 GC) |
+| LUA_TUSERDATA | 8 | 完整 userdata;GC 管理, 载荷为 Value.gc 指向 Udata |
+| LUA_TTHREAD | 9 | 线程/coroutine;GC 管理,载荷为 value.gc 指向 lua_State |
+| LUA_NUMTYPES | 10 | 外部可见类型的计数 |
+
+以下面这段代码为例
+
+```lua
+local f = function(a)
+  print(a)
+end
+```
+
+变量 `f` 对应的 `TValue` 的 `tt_` 是 `LUA_TFUNCTION`, `value_.gc` 指向 `LClosure`
+
+```cpp
+#define CommonHeader	struct GCObject *next; lu_byte tt; lu_byte marked
+
+typedef struct GCObject {
+  CommonHeader;
+} GCObject;
+
+typedef struct Udata {
+  CommonHeader;
+  // some proprety else ...
+} Udata;
+
+typedef struct TString {
+  CommonHeader;
+  // some property else ...
+} TString;
+
+#define ClosureHeader \
+	CommonHeader; lu_byte nupvalues; GCObject *gclist
+typedef struct LClosure {
+  ClosureHeader;
+  // some property else ...
+} LClosure;
+```
+
+从上述代码不难发现,所有被 GC 管理的对象都有一个 `CommonHeader` 宏定义的属性 
+
+这个 `CommonHeader` 的作用是什么?从 `gco2ts` 可以看出作用
+
+```cpp
+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 */
+  struct UpVal upv;
+};
+
+#define cast_u(o)	cast(union GCUnion *, (o))
+#define gco2ts(o) check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts))
+```
+
+`GCUnion` 是 **所有可回收对象的联合体** ,用于安全地将具体对象指针重解释为 `GCObject*`
+
+所有可回收对象(`TString`/`Udata`/`Closure`/`Table`/`Proto`/`Thread`/`UpVal`)在结构体起始处都含有 `CommonHeader` 该头部的二进制布局一致
+
+`GCUnion` 把所有这些类型放进一个 `union`;`union` 的所有成员起始地址相同,因此 `&((GCUnion*)ptr)->gc` 与 `(GCObject*)ptr` 等价(指向同一内存起点)
+
+| GCObject 的属性 | 作用 |
+| --- | --- |
+| next | 把同类对象(或工作队列)串成单向链表,用于 GC/调度 |
+| tt | 对象外部类型标签(字符串/表/函数/线程/完整 userdata/原型/UpVal 等),供运行时快速判断类型与分派处理 |
+| marked | GC 标志位(颜色与代龄等),用于增量/分代 GC 的标记-清扫与回收策略 |
+
+| ClosureHeader 定义属性 | 作用 |
+| --- | --- |
+| CommonHeader | 不做介绍 |
+| nupvalues | 记录闭包捕获的 `upvalue` 的个数,在 `CClosure` 中决定 `TValue` 数组大小,在 `LClosure` 中决定 `UpVal` 数组大小,在 GC 扫描时依次遍历闭包持有的 `upvalues` |
+| gclist | 增量/分代 GC 的 **工作队列指针** ,用于把对象临时串到灰色队列 |
+
+> `upvalue` 指的是 **被闭包捕获的外层局部变量的绑定(变量单元)** ,是按引用共享的,不是值拷贝
+
+##### StackValue
+
+```cpp
+typedef union StackValue {
+  TValue val;
+  struct {
+    TValuefields;
+    unsigned short delta;
+  } tbclist;
+} StackValue;
+```
+
+`StackValue` 是 **栈槽的专用表示** ,在 `TValue` 的基础上,额外为 **待关闭变量**(to-be-closed) 维护一条链
+
+#### TString
+
+```cpp
+typedef struct TString {
+  CommonHeader;
+  lu_byte extra;  // 词法保留字标记(例如 "and"、"local" 等),用于词法分析快速判定;非保留字为 0
+  lu_byte shrlen;  // 短字符串的长度;若为长字符串则置为 0xFF
+  unsigned int hash; // 32 位哈希值;短字符串在创建时计算;长字符串常在需要时延迟计算
+  union {
+    size_t lnglen;  // 长字符串的长度
+    struct TString *hnext;  // 短字符串在全局字符串表中同桶的链表“下一项”指针
+  } u;
+  char contents[1]; // 变长数据区起点
+} TString;
+```
+
+这里的 `contents` 是 **可变尾部数组** 的老派写法。Lua 在创建 TString 时会按长度多分配内存:对象的总大小为 header 大小 + 实际字符串字节数 + 1(用于结尾的 '\0')
+
+也就是说 `contents` 只是变长区域的起始地址,访问不会月结,实际分配的块比结构声明更大
+
+![](Image/001.png)
+
+```cpp
+#define sizelstring(l)  (offsetof(TString, contents) + ((l) + 1) * sizeof(char))
+
+// 获取真实长度
+totalsize = sizelstring(l);
+// 通过真实长度创建对象
+o = luaC_newobj(L, tag, totalsize);
+```
+
+lua 将 字符串 区分为 **长字符串** 和 **短字符串**,分别走不同的创建过程
+
+区分一个字符串是长字符串还是短字符串,通过长度比较,大于 `LUAI_MAXSHORTLEN` 的是长字符串
+
+> `LUAI_MAXSHORTLEN` 的值为 40
+
+```cpp
+TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
+  if (l <= LUAI_MAXSHORTLEN)  /* short string? */
+    return internshrstr(L, str, l);
+  else {
+    ts = luaS_createlngstrobj(L, l);
+    // some code else ...
+    return ts;
+  }
+}
+```
+
+将字符串分为短/长两类,可在常见短字符串上启用驻留(interning)与快速比较,避免为不常复用的长字符串付出昂贵代价
+
+从比较两个 TString 是否相等,可以看出长字符串和段字符串的比较不同
+
+基于 `LUA_TSTRING` 通过位运算,更新出 `LUA_VSHRSTR` 和 `LUA_VLNGSTR` 两个 `tag`,其实也就是 `tt_`
+
+```cpp
+#define makevariant(t,v)	((t) | ((v) << 4))
+#define LUA_VSHRSTR	makevariant(LUA_TSTRING, 0)  /* short strings */
+#define LUA_VLNGSTR	makevariant(LUA_TSTRING, 1)  /* long strings */
+```
+
+如果是短字符串,那么比较相同的方法直接比较两个 TString 的地址是否相同
+
+```cpp
+#define eqshrstr(a,b)	check_exp((a)->tt == LUA_VSHRSTR, (a) == (b))
+```
+
+如果是长字符串,则通过 `luaS_eqlngstr` 函数进行比较
+
+```cpp
+int luaS_eqlngstr (TString *a, TString *b) {
+  size_t len = a->u.lnglen;
+  lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR);
+  return (a == b) ||  /* 相同的地址 */
+    ((len == b->u.lnglen) &&  /* 与判断长度是否相同,因为后面是逐个比较比较费,先提前判断一下 */
+     (memcmp(getlngstr(a), getlngstr(b), len) == 0));  /* 逐字符串比较 */
+}
+```
+
+除了比较的差别之外, 还有一些其他的区别
+
+- `hash` 的计算时机
+  - 短字符串创建即计算
+  - 长字符只在需要计算时计算
+
+- GC 策略的不同
+  - 短字符串驻留、唯一,复用高,减少重复分配与 GC 压力;死短串可在 GC 时从字符串表清理
+  - 长字符串不驻留,避免字符串表膨胀与插入/查找开销,减少大对象长期保活
+
+对于创建短字符串来说,会先从 `global_State` 中查找是否存在存在 hash 相同的短串,如果存在则更新其 GC 状态,并直接返回
+
+```cpp
+global_State *g = G(L);
+stringtable *tb = &g->strt;
+TString **list = &tb->hash[lmod(h, tb->size)];
+for (ts = *list; ts != NULL; ts = ts->u.hnext) {
+  if (l == ts->shrlen && (memcmp(str, getshrstr(ts), l * sizeof(char)) == 0)) {
+    if (isdead(g, ts)) 
+      changewhite(ts);  
+    return ts;
+  }
+}
+```
+
+> 为避免 `hash` 冲突的出现,使用 `TString**` 表示 `TString*` 数组,将所有 `hash` 相同的 `TString` 都放在同一个桶中
+
+如果没有找到,则创建一个新的 `TString`,无论是长字符串还是短字符串,创建都是通过 `createstrobj` 函数
+
+```cpp
+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;
+}
+```
+
+#### Table
+
+```cpp
+typedef struct Table {
+  CommonHeader;
+  lu_byte flags;        // 1<<p 标记该 Table 有没有设置 meta method
+  lu_byte lsizenode;    // 节点数组大小的对数, sizenode = 2^(lsizenode)
+  unsigned int alimit;  // 数组段(array part)的 边界上限
+  TValue *array;        // 数组段指针,存放 1..alimit 之间的整数键对应的值
+  Node *node;           // 哈希段节点数组的起始指针;空表时指向 dummynode
+  Node *lastfree;       // 指向哈希段中“尚可能为空”的最后位置,便于从后往前找空位
+  struct Table *metatable;
+  GCObject *gclist;
+} Table;
+```
+
+以下面代码为例,可以理解 `flags` 属性的作用,通过将 `flags` 与 `1 << TMS` 进行位运算比较来判断是否实现了某些 `metaMethod`
+
+```cpp
+// meta method 枚举
+typedef enum {
+  TM_INDEX,
+  TM_NEWINDEX,
+  TM_GC,
+  TM_MODE,
+  ...
+};
+
+const TValue *luaT_gettm (Table *events, TMS event, TString *ename) {
+  const TValue *tm = luaH_getshortstr(events, ename);
+  lua_assert(event <= TM_EQ);
+  if (notm(tm)) {  /* no tag method? */
+    events->flags |= cast_byte(1u<<event);  /* cache this fact */
+    return NULL;
+  }
+  else return tm;
+}
+```
+
+关于 `Table` 有两个结构:数组段 和 哈希段
+
+- 数组段,只存放整数键,且大多是稠密区间,从 1 开始到 limit
+- 哈希段,存放非整数或 稀疏/大整数键,哈希冲突用 节点数组 + 单链表 实现
+
 

BIN
image/001.png


+ 12 - 1
src/lua-5.4.8/test/test.lua

@@ -1 +1,12 @@
-print("Hell World, First Lua Source")
+local t = {}
+
+t[1] = 1;
+t[2] = 3;
+print("123")
+t[3] = 3;
+print("123")
+t[100000] = 3;
+
+for index, value in ipairs(t) do
+    print(t);
+end