lua5.3异常机制

1、基本概念

    lua本质上利用C函数来操作LUA虚拟机。LUA虚拟机对于C来说只是在堆上的内存对象。

    lua有自己的运行对象(协程),每个协程有自己的调用栈。

    比如下面的函数:

    function add(x, y)

        return x + y

    end

    如果x或y不能进行加法操作,在调用中就会产生异常。产生了异常,虚拟机需要对异常进行处理,最简单粗暴的处理是直接退出程序。

    但这样肯定是不符合一门现在语言对异常处理能力的要求。lua虚拟机的做法是把异常交给用户之前定义的异常处理函数来进行处理。比如是打印出异常信息,打印出调用栈。用户的异常处理函数处理完以后,虚拟机还需要恢复到正常的执行流程中。但是恢复到哪一层的调用栈,不同时候是不一样的。虚拟机会从上一层函数开始,直到能找到一个异常恢复点。找到异常恢复点以后,虚拟机将程序状态恢复正常,并从恢复点再次开始执行。

2、普通调用

    function main()

        local c = add(1, nil)

        print(c)

    end


   假如上述main函数在调用时调用栈为

   main()

   add()

    在add函数中出现异常,由于add的上一层没有异常恢复点,异常会继续向上抛出。后面的print语句不会被执行。

3、使用安全调用

    function main()

        local c

        local success, c = pcall(add, x, y)

        if success then

              print("c is ", c)

        else

            print("exception accur")

        end

    end

    上面修改过的main使用了安全调用的方式,调用add之前,生成了一个恢复点,当add中出现异常,向上抛出时,遇到了恢复点,程序恢复到了正常执行状态。当调用中没有异常发生,pcall会返回true和add的返回值;当发生异常时,pcall会返回false。

4、使用函数安全调用并使用自定义的异常处理函数

    在lua.c中异常发生时,默认的异常处理函数会调用luaL_traceback。它会先打印出异常信息,然后再打印出调用栈。在lua代码中,如果需要指定自己的异常处理函数,可以使用xpcall,以第二个参数中传入自己的异常处理函数。

    exp = function (msg) print(msg) end

    function main()

        local c

        local success, c = xpcall(add, exp, x, y)

        if success then

              print("c is ", c)

        else

              print("exception accur")

        end

    end

    在传入自己的异常处理函数之后,在发生异常时,虚拟机会调用该函数,而不是上层的处理函数。

5、主动抛出异常

    在lua代码中可以简单通过error函数抛出异常。assert函数相当于对error函数进行了包装,通过条件判断是否需要抛出异常。

    function add(x, y)

        if type(x) ~= "number" then

            error("x is not a number")

        end

        if type(y) ~= "number" then

            error("y is not a number")

        end

    end

6、在C接口中抛出异常

    以math.sin库为例,调用发luaL_checknumber函数,如果参数不是一个数值,则会使用luaL_error抛出异常。

    static int math_sin (lua_State *L) {
        lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1)));
        return 1;
    }

    抛出异常的接口函数大概有:

    LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg);

    LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);

    LUA_API int   (lua_error) (lua_State *L);

7、异常恢复点设置

    函数luaD_rawrunprotected负责建立异常恢复点,具体是通过c语言的setjump设置一个回跳点,出现异常后,C层直接longjump跳转到恢复点。而lua层的栈需要由调用luaD_rawrunprotected的函数自行处理。/**
 * u 其实是个 Calls 结构指针
 * old_top: u 中封装的真正调用函数的栈位置
 * ef: 错误处理函数的位置
 * 
 * 将会以保护模式执行 func(L, u);
 */
int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) {
    int status;
    CallInfo *old_ci = L->ci;
    lu_byte old_allowhooks = L->allowhook;
    unsigned short old_nny = L->nny;
    ptrdiff_t old_errfunc = L->errfunc;
    L->errfunc = ef;
    status = luaD_rawrunprotected(L, func, u);
    if (status != LUA_OK) {  /* an error occurred? */
        /* 执行了函数之后栈可以增长, 以前的地址就变了, 所以存成偏移量 */
        StkId oldtop = restorestack(L, old_top);
        luaF_close(L, oldtop);  /* close possible pending closures */
        seterrorobj(L, status, oldtop); // 将L->top设置回之前的L->top加1,新加的1存放当前栈前异常信息
        L->ci = old_ci; // 恢复到恢复点的函数栈侦
        L->allowhook = old_allowhooks;
        L->nny = old_nny;
        luaD_shrinkstack(L);
    }
    L->errfunc = old_errfunc;
    return status;
}


猜你喜欢

转载自blog.csdn.net/yuanfengyun/article/details/53454488