Lua 教程 - 第八章:错误处理与调试
第八章:错误处理与调试
在编程过程中,错误是不可避免的。Lua 提供了几种处理错误和调试代码的机制,帮助我们编写更健壮的程序。
Lua 将错误主要分为两类:语法错误(Syntax Errors)和运行时错误(Runtime Errors)。本章主要讨论运行时错误的捕获与处理。
8.1 错误处理
error 函数
我们可以使用 error 函数显式地抛出一个错误,终止程序的执行。
print("Enter a number:")
local n = io.read("n")
if not n then
error("Invalid input! Not a number.")
end
assert 函数
assert(v [, message]) 检查第一个参数 v 是否为真(非 false 且非 nil)。如果是真,返回 v;如果是假,抛出一个错误,错误信息为 message(默认为 “assertion failed!”)。
local n = assert(io.read("n"), "invalid input")
相当于:
local n = io.read("n")
if not n then
error("invalid input")
end
pcall (Protected Call)
pcall 用于以保护模式调用函数。如果函数执行过程中发生错误,pcall 会捕获错误并返回 false 和错误信息,而不会导致整个程序崩溃。如果执行成功,返回 true 和函数的返回值。
语法:status, result = pcall(func, arg1, arg2, ...)
local function div(a, b)
if b == 0 then error("division by zero") end
return a / b
end
local status, result = pcall(div, 10, 2)
if status then
print("Result:", result) -- Result: 5.0
else
print("Error:", result)
end
local status, result = pcall(div, 10, 0)
if status then
print("Result:", result)
else
print("Error:", result) -- Error: division by zero
end
xpcall
xpcall(func, msgh, arg1, ...) 类似于 pcall,但多了一个错误处理函数 msgh(Message Handler)。当发生错误时,Lua 会在栈展开(Stack Unwinding)之前调用 msgh,这使得我们可以获取调用栈信息。
local function myErrorHandler(err)
print("Error occurred:", err)
print(debug.traceback()) -- 打印调用栈
return err
end
local function faulty()
error("Something went wrong")
end
local status = xpcall(faulty, myErrorHandler)
print("Status:", status) -- Status: false
8.2 调试 (Debug)
Lua 提供了一个名为 debug 的标准库,包含许多用于调试的功能。
常用 debug 函数
-
debug.traceback([message]): 返回当前调用栈的字符串表示。 -
debug.getinfo(func [, what]): 返回关于函数的信息表(如源文件名、行号、参数个数等)。 -
debug.getlocal(level, index): 获取局部变量的名称和值。 -
debug.setlocal(level, index, value): 设置局部变量的值。 -
debug.getupvalue(func, index): 获取闭包的上值(Upvalue)。 -
debug.setupvalue(func, index, value): 设置闭包的上值。
简易调试器示例
我们可以利用 debug.debug() 进入一个交互式调试环境,但这通常用于开发阶段手动调试。在代码中,我们可以编写一个简单的钩子(Hook)来跟踪程序执行。
debug.sethook(function(event, line)
local info = debug.getinfo(2)
print(event, info.short_src, line)
end, "l") -- "l" 表示每行代码执行时触发
性能分析 (Profiling)
通过 debug.sethook,我们还可以统计函数调用的次数或耗时,从而进行性能分析。
local Counters = {}
local Names = {}
local function hook()
local f = debug.getinfo(2, "f").func
if Counters[f] == nil then
Counters[f] = 1
Names[f] = debug.getinfo(2, "Sn")
else
Counters[f] = Counters[f] + 1
end
end
debug.sethook(hook, "c") -- "c" 表示函数调用时触发
-- 运行一些代码
function foo() end
function bar() foo() end
bar()
bar()
-- 打印统计结果
for f, count in pairs(Counters) do
print(Names[f].name or "anonymous", count)
end
8.3 常见错误与解决方案
-
attempt to index a nil value: 尝试对
nil进行索引操作(例如t.k或t[k]),通常是因为表未初始化或键不存在。 -
attempt to call a nil value: 尝试调用一个
nil值(例如f()),通常是因为函数名拼写错误或变量未赋值。 -
stack overflow: 栈溢出,通常是因为无限递归且未进行尾调用优化。
-
C stack overflow: C 栈溢出,通常是因为过深的递归调用(即使是尾调用也可能触发 C 栈限制,但这在纯 Lua 中较少见)。
练习题
-
编写一个函数
safe_read_number(),使用pcall尝试读取用户输入的数字,如果输入非法,提示用户重新输入,直到成功为止。 -
使用
xpcall捕获一个除以零的错误,并在错误处理函数中打印出发生错误的文件名和行号。 -
研究
debug.getinfo函数,编写一个辅助函数print_caller_info(),打印出调用该函数的上层函数的名字。
下一章预告:Lua 的标准库虽然小巧,但涵盖了数学、操作系统、表、字符串等常用功能。下一章我们将通过实例来学习这些标准库的使用。

