添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

距离上一次更博过去了四个月,这四个月来几乎没有任何新的学习,非要找个理由的话,那就是995的工作实在是太特么累了。。从965的公司出来,半年了都还没完全适应7小时工作制到8小时工作制的转变,还要接受这种每天加班的生活,真是一种煎熬

吐槽归吐槽,找借口归找借口,最近的一件事也是给我这个咸鱼注入了一丝能量,开始恢复学习的日子吧

这一篇说明下上一篇中留下的坑:pcall/xpcall/error 几个函数,顺便学习了一下 Lua 的 debug 库

错误处理(Error Handling)

借书中的话说,Lua 是一种扩展语言,经常嵌入到应用程序中,所以在发生错误的时候不能简单地就崩溃或者退出,而是应该结束掉当前的错误块,并返回到程序中

Lua 的错误处理经常用到下面这三个函数

1. error

error 函数会终止最后一个被调用的保护函数,并返回消息作为错误对象

error 函数有两个参数:

第一个参数为自己定义的错误内容

第二个参数 level 会指出错误的位置,默认为1,即调用 error 函数的位置,level = 2 时,指调用(调用 error 的函数)的函数的位置,使用0时代表不输出错误位置

function errFunc()
	error("error occurred",1)
errFunc()
--指出在第2行
function errFunc()
	error("error occurred",2)
errFunc()
--指出在第4行

2. pcall

如果需要处理错误,可以使用 pcall 函数

pcall 函数有两个参数:

第一个参数为要执行的函数

第二个参数为传递给要执行函数的入参

pcall 函数会在保护模式(protected mode)下调用第一个参数,所以 可以捕获到该函数运行过程中的错误。如果没有错误,pcall 函数会返回 true 和执行函数的返回值;否则,会返回 false 和错误信息

function callFunc()
	return "return value"
print(pcall(callFunc))
--true	return value
function callFunc()
	error("error occurred")
	return "return value"
print(pcall(callFunc))
--false   stdin:2: error occurred

3. xpcall

如果想在处理错误时获得更多的信息或做出更多的处理,那可以使用 xpcall 函数,xpcall 函数除了接收要执行的函数外,还可以接收一个错误处理函数(Error Handler Function),Lua 会在堆栈被展开之前调用错误处理函数,所以可以在该函数中使用 Debug 库来收集想要的信息

例如用 debug.traceback() 打印出相关的堆栈

function errFunc()
	print(a.b)
function traceBackFunc(msg)
	print(debug.traceback(msg))
xpcall(errFunc,traceBackFunc)
stdin:2: attempt to index a nil value (global 'a')
stack traceback:
        stdin:2: in function 'traceBackFunc'
        stdin:2: in function 'errFunc'
        [C]: in function 'xpcall'
        stdin:1: in main chunk
        [C]: in ?
  
  • source

    该函数定义的位置,如果该函数通过 loadstring 定义,source 就是那个字符串。如果该函数定义在一个文件中,source 就是该文件名,并且以一个@符号开头

  • short_src

    最多60个字符的 source,对于错误信息很有用

  • linedefined

    该函数被定义的行数

    该函数是什么,可能是”Lua”——一个普通的 Lua 函数,可能是”C”——一个 C 函数,也可能是”main”——一个 Lua 代码块的 main 部分

    该函数的名称

  • namewhat

    函数名称的含义,这个字段可能是”global” “local” “method” “field”或者为空字符串”“,为空字符串时,意味着 Lua 没有找到叫这个名称的函数

    该函数的 upvalues 的个数

    该函数本身

    while true do local info = debug.getinfo(level, "Sl") if not info then break end if info.what == "C" then print(level, "C function") print(string.format("[%s]:%d", info.short_src, info.currentline)) level = level + 1

    2. debug.getlocal/debug.setlocal

    使用 debug.getlocal 函数,可以获得任何活动的函数中的局部变量,该函数有两个入参:所查看的函数的堆栈级别、变量的索引,函数会返回两个值:变量的名称和值。如果变量的索引超出了变量的个数,则返回 nil;如果函数的堆栈级别无效,则会引发一个错误

    同样,这里附上书中的栗子:

    function foo (a,b)
    	local x
    	do local c = a - b end
    	local a = 1
    	while true do
    		local name, value = debug.getlocal(1, a)
    		if not name then break end
    		print(name, value)
    		a = a + 1
    foo(10, 20)
    

    执行的结果为:

    a   10
    b   20
    x   nil
    a   4
    

    函数 foo 的入参为最先的变量,其次是函数内部的局部变量,变量 c 和 getlocal 不在同一个作用域,所以并没有输出

    除此之外,还可以使用 debug.setlocal 函数修改局部变量,前两个入参的含义和 debug.getlocal 相同,第三个入参是想要设置的新值,该函数返回对应变量的名称,如果索引超出,则返回nil

    稍微修改一下上面的栗子:

    function foo (a,b)
    	local x
    	local index = 1
    	while true do
    		local name, value = debug.getlocal(1, index)
    		if not name then break end
    		print("before change:",name,value)
    		--修改变量的值为对应索引值
    		debug.setlocal(1,index,index)
    		name, value = debug.getlocal(1, index)
    		print("after change:",name,value)
    		index = index + 1
    foo(10, 20)
    

    此时可以看到局部变量会被修改:

    before change:	a	10
    after change:	a	1
    before change:	b	20
    after change:	b	2
    before change:	x	nil
    after change:	x	3
    before change:	index	4
    after change:	index	4
    

    3. debug.getupvalue/debug.setupvalue

    debug 库还提供了访问 upvalue 的方法,就是 debug.getupvalue 函数,这个函数的第一个参数是一个函数,更确切的说是一个闭包(闭包这个概念一直不是非常清楚,之前做 javascript 的时候也没了解过,先留个坑,下一篇学学闭包这个东西),第二个参数是 upvalue 的索引

    关于这个 upvalue 的翻译,从官方解释来看,就是内部函数所访问的(外部)变量,对内部函数而言,称之为 upvalue 或外部局部变量(external local variable)

    这篇博客里有看到了一句觉得更白话(准确不准确暂时不知)的解释:函数里用到的定义在该函数之前的local变量,就成为了该函数的upvalue

    这里有一个使用 debug.getupvalue 的栗子:

    function newCounter()
    	local n = 0
    	return function()
    		n = n + 1
    		return n
    c = newCounter()
    local i = 1
    repeat
    	name, val = debug.getupvalue(c, i)
    	if name then
    		print ("index", i, name, "=", val)
    		i = i + 1
    until not name
    

    这段代码输出的结果是:

    index	1	n	=	2
    

    getupvalue 函数输出了函数 c 的 upvalue n

    setupvalue 函数和上面的 setlocal 函数类似,前两个参数和 getupvalue 函数一致,第三个参数是要设置的新值,这里就不再多解释了

    4. hook

    Lua 的 hook 机制允许我们注册一个在程序运行过程中在特定事件下调用的函数,有四种类型的事件可以触发钩子:

    当 Lua 每调用一个函数时触发

  • return

    每当一个函数返回时触发

    当 Lua 开始执行新的一行代码时触发

  • count

    当 Lua 执行了指定次数的指令后触发

    设置钩子可以使用 debug.sethook 函数,该函数的第一个入参就是钩子函数;第二个入参是一个字符串,用来表示我们想监控哪些事件, 对于 call/return/line 事件,我们使用它们的第一个字母(`c`/`r`/`l`)来代替;如果是监控 count 事件,则只需要传递计数次数作为第三个入参即可

    另外,如果想停掉钩子,一样是使用 sethook 函数,不传递任何参数就可以了

    一个简单的栗子:

    function test()
    	local a = 0
    	for i = 0, 100 do
    		a = a + 1
    		print("a="..a)
    function hook(why)
    	error("hook reached: " .. why)
    debug.sethook (hook, "", 100)
    test()
    

    我自己的环境测试时,a 大概打印到13时就触发 hook 函数,所以这个 count 事件所谓的指令执行,就是指各种函数执行、赋值、加减、打印等操作

    另一个简单的栗子:

    function f()
    	function g() end
    function hook (why)
    	print ("hook reached: ", why)
    	print ("function =", debug.getinfo(2, "n").name)
    debug.sethook(hook, "c", 0)
    

    这个栗子会打印出执行的函数:

  •