根据lua5.1.4版 的源码分析其函数调用的原理。
函数调用分为受保护调用和不受保护的调用。
lua函数的调用原理主要分为:
(1)受保护调用,用C层面的堆栈来保护和恢复状态
(2)不受保护的调用,使用自定义的栈存储结构CallInfo数组作为调用栈,修正数据栈,然后把字节码的执行位置跳转到被调用的函数开头,
lua 函数的return操作则做了相反的操作,恢复数据栈,弹出 CallInfo ,修改字节码的执行位置,恢复到原有的执行序列上。
本文目录:
1、受保护的函数调用2、不受保护的函数调用
(1)luaD_precall 环境保存
(2)luaD_poscall 环境恢复
3、函数调用使用实例
(1)不受保护的函数调用
(2)受保护的函数调用
本文内容:
1、受保护的函数调用
受保护的函数调用用C层面的堆栈来保护和恢复状态。
受保护的函数调用可以看成是一个 C 层面意义上完整的过程。在 lua 代码中,pcall是用函数而不是语
言机制完成的。受保护的函数调用一定在 C 层面进出了一次调用栈。它使用独立的一个内部 API luaD_pcall
来实现。公开API 仅需要对它做一些封装即可。
源代码lua5.14版 ldo.c :luaD_pcall(369行)
LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) {
struct CallS c;
int status;
ptrdiff_t func;
lua_lock(L);
api_checknelems(L, nargs+1);//检查栈上元素个数
checkresults(L, nargs, nresults);
if (errfunc == 0)
func = 0;
else {
StkId o = index2adr(L, errfunc);
api_checkvalidindex(L, o);
func = savestack(L, o);
}
c.func = L->top - (nargs+1); /* function to be called */
c.nresults = nresults;
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
adjustresults(L, nresults);
lua_unlock(L);
return status;
}
int luaD_pcall (lua_State *L, Pfunc func, void *u,
ptrdiff_t old_top, ptrdiff_t ef) {
int status;
unsigned short oldnCcalls = L->nCcalls;
ptrdiff_t old_ci = saveci(L, L->ci);
lu_byte old_allowhooks = L->allowhook;
ptrdiff_t old_errfunc = L->errfunc;
L->errfunc = ef;
status = luaD_rawrunprotected(L, func, u);
if (status != 0) { /* an error occurred? */
StkId oldtop = restorestack(L, old_top);
luaF_close(L, oldtop); /* close eventual pending closures */
luaD_seterrorobj(L, status, oldtop);
L->nCcalls = oldnCcalls;
L->ci = restoreci(L, old_ci);
L->base = L->ci->base;
L->savedpc = L->ci->savedpc;
L->allowhook = old_allowhooks;
restore_stack_limit(L);
}
L->errfunc = old_errfunc;
return status;
}
从这段代码我们可以看到pcall的处理模式:用C层面的堆栈来保护和恢复状态。
ci(调用栈)、allowhooks(钩子)、errfunc(函数错误时的栈索引)都保护在luaD_pcall的C堆栈上,一旦出错(luaD_rawrunprotected判断),就可以恢复lua的运行环境。
luaD_rawrunprotected没有正确返回是,需要根据old_top找到堆栈上的刚才调用的函数,给它做弹栈操作。因为luaD_rawrunprotected调用的是一个函数对象,而