defer
1 概述
本章节主要介绍Gs语言中的defer关键字,涵盖其核心概念、语法规则、使用方法和实际应用场景。内容从基础语法到高级特性,从正确使用到常见陷阱,全面解析这一重要的延迟执行机制。本章节面向刚开始学习GS语言的初学者,希望掌握 基础defer语句以进行资源管理。通过阅读本章节,您将理解defer关键字的核心概念和执行机制,掌握defer的正确声明方法、作用域规则与执行顺序。
2 defer 概念
defer的设计理念基于资源申请的就近释放原则,通过在申请资源的代码位置附近立即声明释放操作,有效避免了资源泄漏的风险。同时,在面对具有多个返回路径的复杂函数时,defer能够统一处理清理逻辑,减少代码重复,增强可维护性。
defer的核心概念是延迟执行。当程序执行到一个defer语句时,不会立即执行其后的代码,而是将这些代码(或代码块)注册为一个延迟执行单元。这个单元会在当前代码块退出时(无论是正常退出还是异常退出)被自动执行。
3 defer 基础
3.1 defer 的声明
defer关键字后可以跟随两种形式的代码结构:
简单语句形式,即defer字段后紧跟需要延迟执行的单行代码:
file file_handle = file.open("./config.txt", "rw");
defer file_handle.close();
sync_object mutex = sync_object.create_mutex();
mutex.lock();
defer mutex.unlock();
defer printf("Function execution ends."););
示例3-1:defer简单语句示例
代码块形式,即defer字段后大括号{...}包含的多行延迟执行胆码:
file file_handle = file.open("./config.txt", "rw");
sync_object mutex = sync_object.create_mutex();
mutex.lock();
defer
{
file_handle.close();
mutex.unlock();
printf("Function execution ends.");
}
两种形式在功能上是等价的,选择取决于代码复杂度和可读性需求。简单语句适用于单一操作,代码块适用于需要多条语句的复杂清理逻辑。
3.2 defer 的作用域
defer后面跟着的语句或者代码块都被看做是一个代码单元(闭包函数),都将在当前代码块退出的时候执行,代码单元中的变量的值是当前代码块退出时的值,所以使用defer关键字时需要特别注意作用域和执行时机以免代码的执行结果和自己的预期不一致。
defer语句的作用域规则如下:
-
变量作用域:
defer代码单元可以访问其声明位置可见的所有变量,且变量默认为引用捕获。 -
值捕获时机:变量值在代码块退出时被捕获,而非声明时。
-
执行时机:在离开当前代码块时立即执行。
-
异常处理:即使下文发生报错也会调用,这是主要利用的点。
-
调用顺序:如有多个
defer语句,调用顺序为声明顺序,不应依赖此项。 -
实际实现:实际声明了一个闭包函数,在运行时执行到
defer语句时,对应的函数被push入特定位置等待代码块退出时执行。
以下示例演示了作用域特性:
int n = 4, i = 0, j = 10;
do {
defer write("defer ", j, "\n"); // j的值在循环退出时确定
j++;
write("source ", i, "\n");
i++;
} while(i < n);
示例3-2:defer 的作用域示例
输出结果:
source 0
defer 11
source 1
defer 12
source 2
defer 13
source 3
defer 14
每次循环结束(相当于当前代码块结束)才执行defer语句,而defer代码块中的变量j的值为循环结束时的值。除了循环以外,在其他代码块中时, defer语句也是在结束当前 block (代码块)时执行,如在if、for、try等代码块中的defer语句在离开这些代码块时就会执行,示例如下:
int num = 0;
int increasing()
{
defer num++;
catch {
defer write("leaving catch block\n");
write("do something\n");
}
write("leaving increasing, return value: ");
return num;
}
write(increasing(), "\n");
write(increasing(), "\n");
write("current num value: ", num);
示例3-3:defer的执行
do something
leaving catch block
leaving increasing, return value: 1
do something
leaving catch block
leaving increasing, return value: 2
current num value: 2
可以发现,catch代码块中的defer语句在离开catch后首先执行,而increasing函数下进行自加的defer语句则是在离开函数之后执行后续语句前执行。即increasing返回了自加之前的num,而后num的值实际上已经自加了1。也就是说,defer完成了在函数返回后修改数据的效果。
理解了defer的用法之后,在使用过程中也要注意避免类似上述操作的代码,因为会降低代码的可读性,而defer关键字的本意是用来执行手动资源管理的。
defer 语句中抛出异常时,异 常将会向上抛出,不会打断其他defer 语句的执行,所有本应被执行的的 defer 语句都会被执行。
3.3 defer 的顺序
当多个defer 语句块出现在同一代码块中时,他们按照先入先出(FIFO)的顺序执行:
defer write("First defer registered\n");
defer write("Second defer registered\n");
defer write("Third defer registered\n");
示例3-4:同一代码块中 defer 顺序示例
输出的结果如下:
First defer registered
Second defer registered
Third defer registered
当多个defer语句在嵌套代码块中时,内部的代码块先结束,其中的defer语句先被执行。如下示例所示:
array arr = [];
do
{
defer arr.push_back(1);
defer arr.push_back(2);
do
{
defer arr.push_back(3); //此代码块会先于外部代码块结束
defer arr.push_back(4); //此代码块会先于外部代码块结束
};
defer arr.push_back(5);
};
write(arr); // 输出结果应为[3, 4, 1, 2, 5]
示例3-5:嵌套代码块defer示例
示例输出的结果如下:
[ /* sizeof() == 5 */
3,
4,
1,
2,
5,
]
3.5 defer 与异常处理
defer所属语句实际被封装为一个闭包函数,运行时在执行到defer声明处时相应函数被push到调用栈上,在离开对应的代码块的时候执行对应的defer函数。
一旦defer封装的函数被push到调用栈上,即使除法异常处理流程,相应的defer函数依然会被正常调用,执行。
而defer 语句中抛出异常时,异常将会向上抛出,不会打断后续其他defer 语句的执行,所有本应被执行的的 defer 语句都会被执行。示例如下:
do
{
defer printf(HIM"Was I executed? %s"NOR, "Yes, i was executed."); // 紫色输出,被执行。
defer
{
error("BBB");
printf(HIY"Was I executed? %s"NOR, "Yes, i was executed."); // 黄色输出,异常抛出,后续代码不被执行。
}
defer printf(HIG"Was I executed? %s"NOR, "Yes, i was executed."); // 绿色输出,前面的defer将异常抛出,后续其他defer正常执行
error("AAA");
};
示例3-6:异常处理与defer
运行上述代码,会发现:
-
error("AAA")异常未影响defer函数的调用 -
与
error("BBB")在同一defer函数中的后续以黄色输出的代码未被执行
当然要注意的是如果异常中断了defer的push过程,相应地defer函数就不会被执行了。比如在上述示例中修改代码以在error("AAA")代码后添加额外的defer语句,相应地语句就不会被执行。
3.6 __result__特殊关键字
由于部分开发者有在函数退出时访问获取函数返回值的需求,__result__ 就是为此实现的。其本质是一个语法糖,其专用于处于非entry入口函数的defer中,用于获取当前函数的返回值。
如下代码
int test_ret()
{
defer {write(__result__)};
return 1;
}
test_ret();
示例 3-7:__result__ 简单示例
输出结果如下:
1
可以看到返回值被 defer 函数正确的获取并输出,示例代码实际等效于如下伪代码:
int test_ret()
{
mixed __result__ = nil;
defer {write(__result__)};
reutrn __result__ = 1;
};
使用__result__需要注意的是:
-
__resulu__必须在defer语句中使用 -
__result__所在defer语句禁止在::entry入口函数中 -
__result__所在defer语句必须在函数推出时执行才能获取到正确的返回值信息 -
__result__严禁通过该语句修改返回值或返回值引用,这是未定义行为 !!!
4 defer 调试
在使用gslang的调试工具时, defer语句的内部也可以正常的添加断点与进行调试,调试过程中defer语句的内部断点在defer语句实际执行时命中,示例代码及动图如下:
示例代码:
defer
{
writeln(HIG"Can we debug break in defer function?"NOR);
}
writeln(HIG"Function call is complete."NOR);
演示gif:

示例3-8:defer调试示例
从示例可以看到添加断点,按下F5执行程序后,gslang成功在外部函数执行完成后命中了defer函数中的断点。命中断点后按下F10单步执行一条语句,defer 语句中的输出被成功输出在了控制台上。
5 拓展阅读
现在我们了解了defer语句的基本特定与机制,若要了解更多defer相关的内容,或部分复杂情况的defer处理,请参阅以下文档链接:
6 总结
defer关键字是 GS 语言中一项强大的延迟执行机制,其核心价值在于通过就近声明释放逻辑确保资源的安全管理,有效防止资源泄漏,同时能够统一处理多返回路径的清理工作,减少代码重复,提高可维护性。
defer支持简单语句和代码块两种形式,变量捕获基于代码块退出时的值而非声明时,执行顺序遵循先进后出原则,且异常不会中断已注册 defer的执行,结合 __result__关键字可访问函数返回值。
👏 恭喜完成程序结构章节的学习,在学习并初步掌握了控制流、函数、异常处理、defer等程序结构控制内容后,让我们来继续学习GS的各类型概念、常用操作、适用场景等内容的学习。