# Lua 

## 初识 Lua

### 循环 与 迭代

#### 普通循环

```lua
for i = 0, 7, 2 do
    print(i)
end
```

> 上述代码的输出是：0，2，4，6

普通循环 写法，定义 i 的初始值为 0，第二个 100 为 i 的终值，第三个 2 为每次循环的步长

#### 闭包函数迭代器

```lua
function list_iter (t)
    local i = 0
    local n = table.getn(t)
    return function ()  
        i = i + 1  
        if i <= n then 
            return t[i] 
        end
    end 
end

t = {10, 20, 30} 
for element in list_iter(t) do 
    print(element) 
end 
```

关于上面的代码，虽然 `list_iter` 返回的是函数，但是对于这种情况来说，lua 的执行逻辑是：每次循环时都执行一次函数，直到函数的返回值为 `nil` 为止

由于闭包的存在，`list_iter`` 函数中的局部变量 i 并不会被释放，因为仍有函数对象持有该对象，知道函数对象被释放的时候才会释放，因此闭包每次执行 i 都会加一，直到遍历完整个 table 之后，循环结束，闭包函数对象被释放

闭包函数迭代器支持状态传递

```lua
local _var = 10;
local _state = 2;

function list_iter (t) 
  local i = 0 
  local n = #t 
  return function (state, var) 
    print("state = " .. state .. " var = " .. var)
    i = i + 1 
    if i <= n then return t[i] end
  end, _state, _var
end

local t = {4, 5, 6}

for index in list_iter(t) do
  print(index)
end
```

输出内容是

```bash
state = 2 var = 10
4
state = 2 var = 4
5
state = 2 var = 5
6
state = 2 var = 6
```

执行逻辑大概是 iter, index, state = list_iter(t),得到 index = 10, state = 2

每次循环时执行 index = iter(state , index) 之后判断 index 是否是 nil，进而判断是执行循环体 还是 跳出循环

#### lua 提供的 pairs 和 ipairs

ipairs函数专门用于遍历数组（即键是从1开始的连续整数的表）。它会按照1,2,3...的顺序遍历表，直到遇到nil值就停止

```lua
local t = {
  [2] = "Tue", 
  [3] = "Wet", 
  ["Mon"] = 1, 
  ["Tue"] = 2, 
  ["Wet"] = 3
}

print("ipairs -------------- ")

for k, v in ipairs(t) do
  print(k .. " " .. v)
end

print("pairs -------------- ")

for k, v in pairs(t) do
  print(k .. " " .. v)
end
```

输出内容如下

```bash
ipairs -------------- 
pairs -------------- 
2 Tue
3 Wet
Tue 2
Mon 1
Wet 3
```

注意，使用 ipairs 并没有执行循环体，因为 ipairs 是从 1 开始查找，按照 1、2、3 ... 进行索引遍历，由于 t 没有索引为 1 的 index，循环在一开始就结束了

### 携程

```cpp
-- 创建协程
co = coroutine.create(function() 
    -- 协程函数体
    print("协程开始")
    coroutine.yield("暂停点") -- 挂起协程
    print("协程恢复")
    return "完成"
end)

-- 恢复执行
-- 第一次恢复 status = true value = 暂停点 
status, value = coroutine.resume(co) 
print(((status and "TRUE") or "FALSE") .. " " .. value)
-- 第二次恢复 status = true value = 完成 
status, value = coroutine.resume(co) 
print(((status and "TRUE") or "FALSE") .. " " .. value)
-- 第三次恢复 status = false value =  cannot resume dead coroutine 
status, value = coroutine.resume(co) 
print(((status and "TRUE") or "FALSE") .. " " .. value)

-- 其他API
-- coroutine.status(co) -- 获取协程状态：running, suspended, dead, normal
-- coroutine.wrap() -- 创建协程并返回函数包装器
-- coroutine.yield() -- 挂起当前正在执行的协程
```

### Metatables 和 Metamethods

Metatables 允许改变 table 的行为，比如两个 table 相加，默认情况下是错误的，但是通过 setmetatble 设置 metatable 并实现 __add 函数即可实现两个 table 的相加操作

lua 提供以下几种 metamethods

- 访问控制：
  - `__index(t, k)`  读 `t[k]` 且原键为 `nil` 时调用
  - `__newindex(t, k, v)` 写 `t[k] = v` 且原键为 `nil` 时调用
  - `__metatable`（保护）
  - `__mode`（弱值示例）

- 可调用与字符串：
  - `__call`（构造/克隆）
  - `__tostring(t)` 字符串上下文时触发

- 序列与拼接：
 - `__len(a)` 在 `#t` 时触发
 - `__concat(a, b)` 通常 `a .. b` 时触发

- 算术：
  - `__unm(a)` 返回同类型结果，对应 -a
  - `__add(a, b)`（加）
  - `__sub(a, b)`（减）
  - `__mul(a, b)`（乘）
  - `__div(a, b)`（除）
  - `__idiv(a, b)`
  - `__mod(a, b)`（余）
  - `__pow(a, b)`（幂）

- 位运算（Lua 5.3+）：
  - `__band`
  - `__bor`
  - `__bxor`
  - `__bnot`
  - `__shl`
  - `__shr`

- 比较：
  - `__eq(a, b)`（相等）
  - `__lt(a, b)`（小于）
  - `__le(a, b)`（小于等于）

- 迭代器（Lua 5.3 支持）：
  - `__pairs`
  - `__ipairs`

```lua
-- Lua

-- 基础结构
local ST = {}
ST.v = 10

-- 元表
local MT = {}

-- 辅助：类型判定与取值/构造
local function isST(x)
  return type(x) == "table" and getmetatable(x) == MT
end

local function val(x)
  local tx = type(x)
  if tx == "number" then return x end
  if tx == "table" and isST(x) then
    return rawget(x, "v")
  end
  error("非法操作数：" .. tostring(x))
end

local function new(v)
  return setmetatable({ v = v }, MT)
end

-- 访问控制
function MT.__index(self, k)
  if k == "val" then                -- 别名：读取 self.val 等同 self.v
    return rawget(self, "v")
  end
  return rawget(self, k)            -- 其他键按原值（nil 时返回 nil）
end

function MT.__newindex(self, k, v)
  if k == "v" then                  -- 仅允许写 v
    rawset(self, "v", v)
  else
    error(("只读字段：%s"):format(tostring(k)))
  end
end

-- 可调用：克隆或重置 v，返回新实例
function MT.__call(self, nv)
  if nv == nil then
    nv = rawget(self, "v")
  end
  return new(nv)
end

-- tostring 与拼接
function MT.__tostring(self)
  return ("ST(v=%s)"):format(tostring(rawget(self, "v")))
end

function MT.__concat(a, b)
  local function S(x)
    return isST(x) and tostring(x) or tostring(x)
  end
  return S(a) .. S(b)
end

-- 长度：返回 v（整数化由调用方决定）
function MT.__len(self)
  return rawget(self, "v")
end

-- 算术
function MT.__unm(a)      return new(-val(a)) end
function MT.__add(a, b)   return new(val(a) + val(b)) end
function MT.__sub(a, b)   return new(val(a) - val(b)) end
function MT.__mul(a, b)   return new(val(a) * val(b)) end
function MT.__div(a, b)   return new(val(a) / val(b)) end
function MT.__idiv(a, b)  return new(val(a) // val(b)) end
function MT.__mod(a, b)   return new(val(a) %  val(b)) end
function MT.__pow(a, b)   return new(val(a) ^  val(b)) end

-- 按位（Lua 5.3+）
function MT.__band(a, b)  return new(val(a) & val(b)) end
function MT.__bor(a, b)   return new(val(a) | val(b)) end
function MT.__bxor(a, b)  return new(val(a) ~ val(b)) end
function MT.__bnot(a)     return new(~val(a)) end
function MT.__shl(a, b)   return new(val(a) << val(b)) end
function MT.__shr(a, b)   return new(val(a) >> val(b)) end

-- 比较
function MT.__eq(a, b) return val(a) == val(b) end
function MT.__lt(a, b) return val(a) <  val(b) end
function MT.__le(a, b) return val(a) <= val(b) end

-- 迭代（Lua 5.2/5.3 支持）
function MT.__pairs(self)
  return next, self, nil
end

function MT.__ipairs(self)
  local function iter(_, i)
    i = i + 1
    local v = rawget(self, "v") or 0
    if i <= v then return i, i end
  end
  return iter, self, 0
end

-- 资源关闭（Lua 5.4）
function MT.__close(self, err)
  rawset(self, "_closed", true)
end

-- 保护与命名
-- MT.__metatable = "locked"
MT.__name = "ST"
-- MT.__mode = "v"  -- 可选：弱值

-- 绑定元表到 ST（让 ST 自身成为实例）
setmetatable(ST, MT)
```

测试代码输出

```bash
local st1 = new(10);
local st2 = new(20);

print(getmetatable(st1))
print(getmetatable(st2))

print(st1 + st2)
print(st1 - st2)
print(st1 * st2)
print(st1 / st2)
print(st1 & st2)
```

### 环境

#### 全局变量 environment

为了简化操作，Lua 将环境本身存储在一个全局变量 `_G` 中

> _G._G 等价于 _G

```lua
for n in pairs(_G) do 
  print(n) 
end

print(_G == _G._G)
```

`_G` 本身也是一个 `table`，可以像操作 `table` 一样操作它

```lua
local declaredNames = {} 
function declare (name, initval)  
    rawset(_G, name, initval)  
    declaredNames[name] = true
end 

setmetatable(_G, {  
    __newindex = function (t, n, v) 
        if not declaredNames[n] then 
            error("attempt to write to undeclared var. "..n, 2) 
        else  
            rawset(t, n, v) -- do the actual set 
        end 
    end,  
    __index = function (_, n) 
        if not declaredNames[n] then 
            error("attempt to read undeclared var. "..n, 2) 
        else  
            return nil
        end
    end, 
}) 
```

像上面这样，给 `_G` 设置 `metatable`，当想要获取空对象是提示错误

> `rawset` 可以不触发 `metamethod` 来给 `table` 设置值

#### 非全局的环境

全局环境的问题就是一个小小的修改，可能会影响所有的程序

Lua 5.0 之后提供 setfenv 函数来改变一个函数的环境

setfenv 接受函数和新的环境作为参数，还接受一个数字表示栈顶的活动函数，比如 1 表示当前函数，2 表示调用当前函数的函数

```lua
a = 1 
setfenv(1, {_G = _G})  -- 此时修改环境 全局变量中有 a 但是局部变量中没有 a 了
_G.print(a) --> nil 环境修改，当前函数中没有局部变量 a
_G.print(_G.a) --> 1 
```

不过 setfenv 和 getfenv 在 Lua 5.2 版本之后被 移除

这里提供了两种替代方案

1. 使用局部 `_ENV` 重新绑定当前代码块环境
2. 使用 `load(chunk, chunkname, mode, env)` 在指定环境中执行 字符串/文件

```lua
a = 1 -- 仍在默认全局环境里 

do 
  local _ENV = { _G = _G } -- 将当前块的环境改为仅含 _G 的表 
  _G.print(a) -- => nil（因为当前块环境里没有 a） 
  _G.print(_G.a) -- => 1（从真正的全局表里读到 a） 
end
```

> 使用 _ENV 临时修改执行环境

```lua
a = 1 

local env = { _G = _G } -- 自定义环境（不含 a） 
local chunk = [[ 
  _G.print(a) -- => nil 
  _G.print(_G.a) -- => 1 
]] 

local f = load(chunk, "demo", "t", env) 

f()
```

> 使用 load 让代码在指定环境中运行

### 面向对象

```lua
local St = {v = 10}
function St.printf(self)
    print(self.v)
end
St.printf(St);
```

类似 `printf` 这样每次调用都要把自己这个对象传入的写法，非常麻烦，而且一点也不 `OOP`，所以 lua 提供 **:** 来代替 . 

```lua
local St = {
    v = 10,
    printf2 = function(self) 
        print(self.v);
      end
}

function St:printf()
  print(self.v);
end

St:printf();
St.printf2(St);
```

> 使用 **:** 来 定义 函数 和 调用 函数

#### 类

参考 JS 中实现 class 的方案，使用原型链的方式来实现继承

```cpp
local St = { 
    v = 10
}

function St:printf()
  print(self.v);
end

function St:Add(val)
  self.v = self.v + val;
end

function St:new (o) 
  o = o or {} -- create object if user does not provide one 
  setmetatable(o, self) 
  self.__index = self 
  return o 
end

local s1 = St:new()

s1:Add(100);

print(s1.v);
print(St.v);
```

- 由于 `s1` 没有名为 `Add` 的函数，会通过 `metatable` 查找，进而使用 `St.Add` 函数
- 在调用 `Add` 函数时，由于 `s1` 没有 `v` 属性，会通过 `metatable` 找到 `St.v`，并通过 `self. v = self.v + val` 语句给 `s1` 添加 `v` 属性，并赋值

> 由于赋值时是给 `s1` 赋值，所以并没有修改 `St.v` 的值

#### 继承

```lua
local StSon = St:new();

function StSon:Mul(val)
  self.v = self.v * val;
end

function StSon:new(o)
  o = o or {}
  setmetatable(o, StSon)
  self.__index = self
  return o
end
```

用例

```lua
local sts1 = StSon:new()

sts1:Add(100);
sts1:Mul(2);

sts1:printf()
```

### Weak 表

由于 lua 有自己的垃圾回收机制，大部分情况下不用关心对象的 GC

`Weak` 表的作用是告诉 GC 一个引用不应该阻止该对象被回收，它是个弱引用，可以被 GC 掉

如何定义一个 `table` 是 `weak table` 呢？

- `metatable` 的 `__mode` 字符串包含小写的 `k` 表示这个 `table` 的 `keys` 都是 `weak` 的
- `metatable` 的 `__mode` 字符串包含小写的 `v` 表示这个 `table` 的 `values` 都是 `weak` 的

```lua
a = {} 
b = {} 
setmetatable(a, b) 

b.__mode = "k" -- 现在 table a 的 keys 都是 weak 的 

key = { a = 1} -- 创建 key
a[key] = 1 
key = { b = 2} -- 重新赋值 key
a[key] = 2 

collectgarbage() -- 强制垃圾回收

for k, v in pairs(a) do 
  print(v) 
end --> 2
```
上述代码，此时只会输出 2 ，不会输出 1 和 2 

这是因为 `table` `a` 的 `keys` 是 `weak` 的，`{ a = 1}` 没有对象引用，被 GC 掉了；`{ b = } `被 `key` 引用，因此没有被 GC

## 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
- 哈希段，存放非整数或 稀疏/大整数键，哈希冲突用 节点数组 + 单链表 实现


