1 最重要的两个数据结构
1.1 lua_State(Lua虚拟机/Lua协程)
每个lua虚拟机(协程)对应一个lua_State结构体
(lstate.h) lua_State
struct lua_State
{
CommonHeader;//#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
lu_byte status;//协程的状态码
StkId top;//栈顶位置,“寄存器”第一个可用位置
StkId base;//当前函数调用的栈基址
global_State* l_G;//全局状态机
CallInfo* ci;//当前函数调用信息
const Instruction* savedpc;//指令指针
StkId stack_last;//“寄存器”最后一个可用位置
StkId stack;//栈数组的起始位置
CallInfo* end_ci;//函数调用信息数组的最后一个位置的下一个位置
CallInfo* base_ci;//函数调用信息数组首地址
int stacksize;//栈的大小
int size_ci;//函数调用信息数组大小
unsigned short nCcalls;//内嵌C调用的层数
unsigned short baseCcalls;//唤醒协程时的内嵌C调用层数
lu_byte hookmask;
lu_byte allowhook;
int basehookcount;
int hookcount;
lua_Hook hook;
TValue l_gt;//Global表
TValue env;//环境表的临时位置
GCObject* openupval;//栈上open状态的uvalues
GCObject* gclist;
struct lua_longjmp* errorJmp;//当前跳转信息,实现try catch的关键结构
ptrdiff_t errfunc;//当前错误处理函数相对于“寄存器数组首地址”的偏移地址
};
1.2 global_State(Lua全局状态)
(lstate.h) global_State
typedef struct global_State
{
stringtable strt;//全局字符串表
lua_Alloc freealloc;//内存重分配函数
void* ud;//freealloc的辅助数据
lu_byte currentwhite;//当前白色,见GC章节
lu_byte gcstate;//GC状态,见GC章节
int sweepstrgc;//strt中GC扫描到的位置
GCObject* rootgc;//所有可回收对象的链表
GCObject** sweepgc;//rootgc中扫描到的位置
GCObject* gray;//灰色对象链表
GCObject* grayagain;//需要被原子性遍历地对象链表
GCObject* week;//弱表的链表
GCObject* tmudata;//需要被GC的userdata的链表的最后一个元素
Mbuffer* buff;//字符串连接操作用的临时缓冲对象
lu_mem GCthreshold;//触发GC的边界值,见GC章节
lu_mem totalbytes;//当前分配的总字节数
lu_mem estimate;//实际上使用的总字节数的估计值
lu_mem gcdept;//在预定的回收字节数中,还欠多少字节没有回收
int gcpause;//连续的GC中的停顿步长相关值,见GC章节
int gcstepmul;//GC的粒度
lua_CFunction panic;//对于未捕获异常,会调用这个函数
TValue l_registry;//注册表
struct lua_State* mainthread;//主协程
UpVal uvhead;//open状态的upvalue双链表的头部
struct Table* mt[NUM_TAGS];//存放 基本类型的元表 的数组
TString* tmname[TM_N];//存放 元方法名字符串对象地址 的数组
}
2 环境相关的变量
2.1 Global表
Global表存放在lua_State结构体中,每个lua_State实例都有一个对应的Global表,对于一个lua_State这个表就是用来存放全局变量的。
2.1.1 Global表在lua_State结构中
struct lua_State
{
//..//
TValue l_gt;//Global表
//..//
}
2.1.2 Global表在 f_luaopen 时被初始化
(lstate.c) f_luaopen (f_luaopen 又被 lua_newstate 调用)
static void f_luaopen(lua_State* L, void* ud)
{
//...//
sethvalue(L, gt(L), luaH_new(L, 0, 2));//创建了一个arraysize=0, hashsize=2的表作为全局表
//...//
}
2.2 env表
env表存放在Closure结构体中,也就是每个函数都有自己独立的环境
2.2.1 env表在Closure结构中
(lobject.h) Closure
#define ClosureHeader \
CommonHeader; \
lu_byte isC; \
lu_byte nupvalues; \
GCObject *gclist; \
struct Table *env //函数的环境表地址
typedef struct CClosure
{
ClosureHeader;
lua_CFunction f;
TValue upvalue[1];
} CClosure;
typedef struct LClosure
{
ClosureHeader;
struct Proto* p;
UpVal* upvals[1];
} LClosure;
typedef union Closure
{
CClosure c;
LClosure l;
} Closure;
2.2.2 查找一个全局变量<=>在当前函数的env表中寻找
(lvm.c) luaV_execute 模拟CPU,后续虚拟机章节细说
//我们暂且只关心 全局变量 的 查找或设置
void luaV_execute (lua_State *L, int nexeccalls)
{
LClosure* cl;
StkId base;
TValue* k;
const Instruction* pc;
reentry:
pc = L->savedpc;
cl = &clvalue(L->ci->func)->l;
base = L->base;
k = cl->p->k;
//循环取指令,执行指令
for (;;) {
const Instruction i = *pc++;//取出当前指令i,pc指向下一个指令,完全符合CPU工作原理
if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE))
{
traceexec(L, pc);//traceexec后面说
if (L->status == LUA_YIELD)
{
L->savedpc = pc - 1;
return;
}
base = L->base;
}
StkId ra = RA(i);//#define RA(i) (base+GETARG_A(i))
//#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))
switch (GET_OPCODE(i))
{
//..//
case OP_GETGLOBAL: //OP_GETGLOBAL 指令格式A Bx 指令意义 R(A) := Gbl[Kst(Bx)]
{
TValue g;
TValue* rb = KBx(i);//#define KBx(i) k+GETARG_Bx(i)
sethvalue(L, &g, cl->env);//g设为去当前函数的env表
Protect(luaV_gettable(L, &g, rb, ra));
continue;
}
//..//
case OP_SETGLOBAL:
{
TValue g;
sethvalue(L, &g, cl->env);
Protect(luaV_settable(L, &g, KBx(i), ra));
continue;
}
//..//
}
}
}
(lvm.c) Protect宏
- 执行代码x前 先将L->savedpc设为 下一步药执行的指令地址pc (因为代码x有异常的可能,所以记录pc到L上以便从下一条指令继续执行)
- 执行代码x
- 执行代码x后,再将 base设为 L->base(一位代码x可能会触发栈的重新分配内存)
#define Protect(x) { L->savedpc = pc; {x;}; base = L->base; }
2.2.3 lua函数的env表何时被设置
- f_parser函数里,env被设置为&L->l_gt(lua_State的Global表)
- luaV_execute的OP_CLOSURE指令分支,env被设置为&clvalue(L->ci->func)->l->env (当前调用信息对应的函数的env表)
(ldo.c) f_parser
static void f_parser(lua_State* L, void* ud)
{
struct SParser* p = cast(struct SParser*, ud);
int c = luaZ_lookahead(p->z);
luaC_checkGC(L);
Proto* tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z, &p->buff, p->name);
//很显然,在解析二进制或原文件时得到的lua函数,默认的env表时L的Global表
Closure* cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
cl->l.p = tf;
for (i = 0; i < tf.nups; i++)
{
cl->l.upvals[i] = luaF_newupval(L);
}
setclvalue(L, L->top, cl);
incr_top(L);
}
(lvm.c) luaV_execute
//我们暂且只关心 OP_CLOSURE 指令
void luaV_execute (lua_State *L, int nexeccalls)
{
LClosure* cl;
StkId base;
TValue* k;
const Instruction* pc;
reentry:
pc = L->savedpc;
cl = &clvalue(L->ci->func)->l;
base = L->base;
k = cl->p->k;
//循环取指令,执行指令
for (;;) {
const Instruction i = *pc++;//取出当前指令i,pc指向下一个指令,完全符合CPU工作原理
if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE))
{
traceexec(L, pc);//traceexec后面说
if (L->status == LUA_YIELD)
{
L->savedpc = pc - 1;
return;
}
base = L->base;
}
StkId ra = RA(i);//#define RA(i) (base+GETARG_A(i))
//#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))
switch (GET_OPCODE(i))
{
//..//
case OP_CLOSURE:
{
//根据索引获取当前函数的内嵌函数原型
Proto* p = cl->p->p[GETARG_Bx(i)];
//获取内嵌函数的upvalue数量
int nup = p->nups;
//根据upvalue数量和env表创建内嵌函数
CLosure* ncl = luaF_newLclosure(L, nup, cl->env);
ncl->l.p = p;
for (int j = 0; j < nup; j++, pc++)
{
if (GET_OPCODE(*pc) == OP_GETUPVAL)
{
ncl->l.upvals[j] = cl->upvals[GETARG_B(*pc)];
}
else
{
ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc));
}
}
setclvalue(L, ra, ncl);
Protect(luaC_checkGC(L));
continue;
}
//..//
}
}
}
(lfunc.c) luaF_newLclosure 创建一个新的lua函数(闭包)
Closure* luaF_newLclosure(lua_State* L, int nupvalues, Table* env)
{
//#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + cast(int, sizeof(TValue *)*((n)-1)))
Closure* cl = cast(Closure*, luaM_malloc(L, sizeLClosure(nupvalues)));
luaC_link(L, obj2gco(cl), LUA_TFUNCTION);
cl->l.isC = 0;
cl->l.env = e;
cl->l.nupvalues = cast_byte(nupvalues);
while (nupvalues--)
{
cl->l.upvals[nupvalues] = NULL;
}
return c;
}
2.2.4 C函数的env表何时被设置
- lua_pushcclosure函数里,env被设置为getcurrenv(L)
- f_Ccall函数里,env被设置为getcurrenv(L)
(lapi.c) lua_pushcclosure 创建一个C闭包入栈
LUA_API void lua_pushcclosure(lua_State* L, lua_CFunction fn, int n)
{
luaC_checkGC(L);
Closure* cl = luaF_newCclosure(L, n, getcurrentv(L));
cl->c.f = fn;
L->top -= n;
while(n--)
{
setobj2n(L, &cl->c.upvalue[n], L->top + n);
}
setclvalue(L, L->top, cl);
api_incr_top(L);//#define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;}
}
(lfunc.c) luaF_newCclosure 创建C闭包
Closure* luaF_newCclosure(lua_State* L, int nupvals, Table* env)
{
//#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + cast(int, sizeof(TValue)*((n)-1)))
Closure* c = cast(Closure*, luaM_malloc(L, sizeCclosure(nupvals)));
luaC_link(L, obj2gco(c), LUA_TFUNCTION);
c->c.isC = 1;
c->c.env = env;
c->c.nupvals = cast_byte(nupvals);
return c;
}
(lapi.c) getcurrentv 获取当前的函数env表(获取L->ci的env表,当然L->base_ci的env表就是Global表)
- 若不是内嵌函数,则返回Global表
- 若是内嵌函数,则返回母函数的env表
static Table* getcurrenv(lua_State* L)
{
if (L->ci == L->base_ci)
{
return hvalue(gt(L));
}
Closure* func = curr_func(L);//#define curr_func(L) (clvalue(L->ci->func))
return func->c.env;
}
(lapi.c) f_Ccall 创建并调用一个C函数
static void f_Ccall(lua_State* L, void* ud)
{
struct CCallS* c = cast(struct CCallS*, ud);
Closure* cl = luaF_newCclosure(L, 0, getcurrenv(L));
cl->c.f = c->func;
setclvalue(L, L->top, cl);
api_incr_top(L);
luaD_call(L, L->top - 2, 0);
}
2.2.5 getfenv(读取函数的环境)
(lbaselib.c) luaB_getfenv (若为C函数,则返回Global表;否则lua_getfenv)
static int luaB_getfenv(lua_State* L)
{
getfunc(L, 1);
if (lua_iscfuntion(L, -1))
{
lua_pushvalue(L, LUA_GLOBALSINDEX);
}
else
{
lua_getfenv(L, -1);
}
return 1;
}
2.2.6 setfenv(强制设置函数的环境)
(lbaselib.c) luaB_setfenv
static int luaB_setfenv(lua_State* L)
{
//检查L->base+2-1处是不是table类型
luaL_chechtype(L, 2, LUA_TTABLE);
getfunc(L, 0);
//把 L->base+2-1 处的值复制到 L->top 处,并L->top++
lua_pushvalue(L, 2);
//若level为0,则改变当前lua线程的环境
if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0)
{
lua_pushthread(L);
lua_insert(L, -2);
lua_settenv(L, -2);
return 0;
}
//若L->top-2 是C函数,或者给 L->top-2 设置env失败,则报错
if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0)
{
//#define LUA_QL(x) "'" x "'"
luaL_error(L, LUA_QL("setfenv") " cannot change environment of given object");
}
return 1;
}
(lauxlib.c) luaL_checktype 检查类型是否满足,否则报错
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t)
{
if (lua_type(L, narg) != t)
{
tag_error(L, narg, t);
}
}
(lauxlib.c) tag_error 根据类型标识报错
static void tag_error(lua_State* L, int narg, int tag)
{
luaL_typeerror(L, narg, lua_typename(L, tag));
}
(lauxlib.c) luaL_typeerror 根据类型名报错
LUALIB_API int luaL_typeerror(lua_State* L, int narg, const char* tname)
{
const char* msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg));
return luaL_argerror(L, narg, msg);//luaL_argerror见异常章节
}
(lbaselib.c) getfunc (获取L->base 处的函数,并压入栈顶)
static void getfunc(lua_State* L, int opt)
{
//若L->base处是函数,则把L->base处的值复制到L->top处,L->top++
if (lua_isfunction(L, 1))
{
lua_pushvalue(L, 1);
return;
}
//#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
//#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
//opt为非0时,对L->base处的值进行判断,若为nil或none,则取默认值,否则执行整数转换操作;若opt为0,则仅仅执行整数转换操作
int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1);
//level必须为>=0
luaL_argcheck(L, level >= 0, 1, "level must be non-negative");
lua_Debug ar;
if (lua_getstack(L, level, &ar) == 0)
{
luaL_argerror(L, 1, "invalid level");
}
lua_getinfo(L, "f", &ar);
if (lua_isnil(L, -1))
{
luaL_error(L, "no function environmnent for tail call at level %d", level);//luaL_error见异常章节
}
}
(lauxlib.c) luaL_optinteger
//不是数字类型且转为整数后不是0,则报错
LUALIB_API lua_chechinteger(lua_State* L, int narg)
{
lua_Integer d = lua_tointeger(L, narg);
if (d == 0 && !lua_isnumber(L, narg))
{
tag_error(L, narg, LUA_TNUMBER);
}
return d;
}
LUALIB_API lua_Integer luaL_optinteger(lua_State* L, int narg, lua_Integer def)
{
//#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
return luaL_opt(L, luaL_checkinteger, narg, def);
}
(ldebug.c) lua_getstack 获取指定层级的调试信息
LUA_API int lua_getstack(lua_State* L, int level, lua_Debug* ar)
{
for (CallInfo* ci = L->ci; level > 0 && ci > L->base_ci; ci--)
{
level--;
//若为lua函数,则跳过尾调用,//见函数章节
if (f_isLua(ci))
{
level -= ci->tailcalls;//原文 /* skip lost tail calls */ 见函数章节
}
}
int status;
if (level == 0 && ci > L->base_ci)
{
//找到了合适的level
status = 1;
ar->i_ci = cast_int(ci - L->base_ci);
}
else if (level < 0)
{
//原文 /* level is of a lost tail call? */ 见函数章节
status = 1;
ar->i_ci = 0;
}
else
{
status = 0;
}
return status;
}
(ldebug.c) lua_getinfo 获取debug信息
LUA_API int lua_getinfo(lua_State* L, const char* what, lua_Debug* ar)
{
Closure* f = NULL;
if (*what == '>')
{
StdId func = L->top - 1;
what++;
f = clvalue(func);
L->top--;//函数出栈
}
else if (ar->i_ci != 0)//没有尾调用 //见函数章节
{
ci = L->base_ci + ar->i_ci;
f = clvalue(ci->func);
}
int status = auxgetinfo(L, what, ar, f, ci);//auxgetinfo,见函数章节
if (strchr(what, 'f'))
{
if (f == NULL)
{
setnilvalue(L->top);
}
else
{
setclvalue(L, L->top, f);
}
incr_top(L);
}
if (strchr(what, 'L'))
{
collectvalidlines(L, f);//收集行号信息
}
return status;
}
(ldebug.c) collectvalidlines 收集行号信息(获得一个table<行号,true> 的结构,压入栈顶)
static void collectvalidlines(lua_State* L, Closure* f)
{
if (f == NULL || f->c.isC)
{
setnilvalue(L->top);
}
else
{
Table* t = luaH_new(L, 0, 0);
int* lineinfo = f->l.p->lineinfo;
for (int i = 0; i < f->l.p->sizelineinfo; i++)
{
setbvalue(luaH_setnum(L, t, lineinfo[i]), 1);
}
sethvalue(L, L->top, t);
}
incr_top(L);
}
2.3 registry表(注册表)
- 注册表在global_State中,全局唯一,这个表可以被多个lua_State访问
- 注册表只能被C代码访问,Lua代码不能访问
(lauxlib.h) lua_ref,lua_unref,lua_getref
#define LUA_NOREF (-2)
#define LUA_REFNIL (-1)
//往注册表新增一个唯一key
#define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \
(lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0))
//取消注册表一个唯一key
#define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref))
//获取注册表唯一key对应的值
#define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))
(lauxlib.c) luaL_ref,luaL_unref
//对于栈顶的value,从表t中获取一个可用key,来存value
/*
表中的key=FREELIST_REF对应的value是 空闲的key的索引(而所谓的空闲的key是由于unref造成的结果)
举个例子来理解这个设计:
开始t={[FREELIST_REF]=nil}
存v1: ref=t[FREELIST_REF]=nil => ref=#t + 1 = 1, t[ref]=t[1]=v1 => t={[FREELIST_REF]=nil, [1]=v1}
存v2: ref=t[FREELIST_REF]=nil => ref=#t + 1 = 2, t[ref]=t[2]=v2 => t={[FREELIST_REF]=nil, [1]=v1, [2]=v2}
unref(1): ref=1, t[ref]=t[FREELIST_REF]=nil, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=nil, [2]=v2}
unref(2): ref=2, t[ref=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=nil, [2]=1}
存v3: t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=1, t[ref]=v3 => t={[FREELIST_REF]=1, [1]=nil, [2]=v3}
存v4: t[FREELIST_REF]=1 => ref=1, t[FREELIST_REF]=t[ref]=nil, t[ref]=v4 => t={[FREELIST_REF]=nil, [1]=v4, [2]=v3}
也就说t中的key分为2种状态:空闲 和 非空闲。若为空闲,则其value是下一个空闲key的索引;若为非空闲,则其value是有意义的值。
拓展讨论:
开始t={[FREELIST_REF]=nil}
存v1: t[FREELIST_REF]=nil => ref=#t + 1 = 1, t[ref]=t[1]=v1 => t={[FREELIST_REF]=nil, [1]=v1}
存v2: t[FREELIST_REF]=nil => ref=#t + 1 = 2, t[ref]=t[2]=v2 => t={[FREELIST_REF]=nil, [1]=v1, [2]=v2}
unref(1): ref=1, t[ref]=t[FREELIST_REF]=nil, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=nil, [2]=v2}
再次unref(1): ref=1, t[ref]=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=1, [2]=v2}
unref(2): ref=2, t[ref]=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=1, [2]=1}
再次unref(2): ref=2, t[ref]=t[FREELIST_REF]=2, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=1, [2]=2}
存v3: t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=2, t[ref]=v3 => t={[FREELIST_REF]=2, [1]=1, [2]=v3}
存v4: t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=v3, t[ref]=v4 => t={[FREELIST_REF]=2, [1]=1, [2]=v4}//v3不见了,出问题了!!!
*/
LUALIB_API int luaL_ref(lua_State* L, int t)
{
//假设栈顶的值为v
//若v==nil,则返回-1
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
return LUA_REFNIL;//#define LUA_REFNIL (-1)
}
t = abs_index(L, t);//#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
lua_rawgeti(L, t, FREELIST_REF);//#define FREELIST_REF 0 /* free list of references */
//ref=t[FREELIST_REF], 获取一个空闲的key
int ref = (int)lua_tointeger(L, -1);
lua_pop(L, -1);
if (ref != 0)//ref!=0,说明有空闲key
{
lua_rawgeti(L, t, ref);
lua_rawseti(L, t, FREELIST_REF);//t[FREELIST_REF] = t[ref]
}
else//ref==0,说明没有空闲位置,需要新建key
{
ref = (int)lua_objlen(L, t);
ref++;//创建一个新的引用
}
lua_rawseti(L, t, ref);//t[ref]=v
return ref;
}
LUALIB_API void luaL_unref(lua_State* L, int t, int ref)
{
if (ref < 0)
{
return;
}
t = abs_index(L, t);
lua_rawgeti(L, t, FREELIST_REF);
lua_rawseti(L, t, ref);//t[ref]=t[FREELIST_REF]
lua_pushinteger(L, ref);
lua_rawseti(L, t, FREELIST_REF);//t[FREELIST_REF]=ref
}
(lapi.c) lua_objlen 获取对象的长度
LUA_API size_t lua_objlen(lua_State* L, int idx)
{
StkId o = index2adr(L, idx);
switch(ttype(o))
{
case LUA_TSTRING:
{
return tsvalue(o)->len;
}
case LUA_TUSERDATA:
{
return uvalue(o)->len;
}
case LUA_TTABLE:
{
return luaH_getn(hvalue(o));
}
case LUA_TNUMBER:
{
size_t l = luaV_tostring(L, o) ? tsvalue(o)->len : 0;
return l;
}
default:
{
return 0;
}
}
}
2.4 UpValue
registry表是全局变量的存储,env表是函数内全局变量的存储,UpValue则是函数内静态变量的存储
通过 index2adr(L, int idx) (其中idx<LUA_GLOBALSINDEX)获取C闭包的upvalue地址
至于UpValue的一切,见函数章节。