`setjmp` 和 `longjmp` 的安全使用
Safe usage of `setjmp` and `longjmp`
我知道人们总是说不要使用 longjmp
,它很邪恶,很危险。
但我认为它对于退出深度 recursions/nested 函数调用很有用。
单个 longjmp
是否比大量重复检查和 returns 像 if(returnVal != SUCCESS) return returnVal;
更快?
至于安全,只要动态内存等资源释放得当,应该没有问题吧?
到目前为止,使用 longjmp
似乎并不困难,它甚至使我的代码更简洁。我很想经常使用它。
(恕我直言,在许多情况下,首先在深度递归中没有分配动态 memory/resources。深度函数调用似乎更常见于数据 parsing/manipulation/validation。动态分配通常发生在更高的位置级别,在调用出现 setjmp
的函数之前。)
setjmp
和 longjmp
可以看作是穷人的 exception mechanism. BTW, Ocaml 异常与 setjmp
一样快,但语义更清晰。
当然 longjmp
比在中间函数中重复返回错误代码要快得多,因为它会弹出一个可能很重要的 call stack 部分。
(我暗中关注Linux)
只要它们之间没有分配资源,它们就是有效和有用的,包括:
- 堆内存(
malloc
)
fopen
-ing FILE*
句柄
- 打开操作系统文件描述符(例如套接字)
- 其他操作系统资源,例如计时器或信号处理程序
- 获取由某个服务器管理的一些外部资源,例如X11 windows(因此使用任何小部件工具包,如 GTK),或数据库句柄或连接...
- 等...
主要问题是 属性 而不是 leaking resources is a global whole-program property (or at least global to all functions possibly called between setjmp
and longjmp
), so it prohibits modular software development:任何其他同事必须在 setjmp
和 [=11] 之间的任何函数中改进某些代码=] 必须意识到该限制并遵守该纪律。
因此,如果您使用 setjmp
,请非常清楚地记录下来。
顺便说一句,如果你只关心malloc
,系统地使用 Boehm's conservative garbage collector会有很大帮助;您将在任何地方使用 GC_malloc
而不是 malloc
,并且您不会关心 free
,实际上这就足够了;那么您可以毫无顾虑地使用 setjmp
(因为您可以在 setjmp
和 longjmp
之间调用 GC_malloc
)。
(请注意,垃圾收集器的概念和术语与异常处理和setjmp
非常相关,但很多人对它们的了解还不够。阅读Garbage Collection Handbook应该是值得的)
另请参阅 RAII and learn about C++11 exceptions (and their relation to destructors). Learn a bit about continuations and CPS。
阅读 setjmp(3), longjmp(3) (and also about sigsetjmp
, siglongjmp
, and setcontext(3)) 并注意编译器必须了解 setjmp
您应该注意,在某些情况下调用 setjmp 并不能保证安全(例如,您不能可移植地存储 setjmp 的 return 值)。
此外,如果您想在同一函数中调用 setjmp 后访问局部变量,您应该将这些变量标记为 volatile。
使用 setjmp 和 longjmp 也很有用,因为如果递归导致堆栈溢出,您可以使用来自信号处理程序的 longjmp 恢复(不要忘记设置备用堆栈)和 return 错误反而。如果你想这样做,你应该考虑使用 sigsetjmp 和 siglongjmp 来保存信号配置。
我知道人们总是说不要使用 longjmp
,它很邪恶,很危险。
但我认为它对于退出深度 recursions/nested 函数调用很有用。
单个 longjmp
是否比大量重复检查和 returns 像 if(returnVal != SUCCESS) return returnVal;
更快?
至于安全,只要动态内存等资源释放得当,应该没有问题吧?
到目前为止,使用 longjmp
似乎并不困难,它甚至使我的代码更简洁。我很想经常使用它。
(恕我直言,在许多情况下,首先在深度递归中没有分配动态 memory/resources。深度函数调用似乎更常见于数据 parsing/manipulation/validation。动态分配通常发生在更高的位置级别,在调用出现 setjmp
的函数之前。)
setjmp
和 longjmp
可以看作是穷人的 exception mechanism. BTW, Ocaml 异常与 setjmp
一样快,但语义更清晰。
当然 longjmp
比在中间函数中重复返回错误代码要快得多,因为它会弹出一个可能很重要的 call stack 部分。
(我暗中关注Linux)
只要它们之间没有分配资源,它们就是有效和有用的,包括:
- 堆内存(
malloc
) fopen
-ingFILE*
句柄- 打开操作系统文件描述符(例如套接字)
- 其他操作系统资源,例如计时器或信号处理程序
- 获取由某个服务器管理的一些外部资源,例如X11 windows(因此使用任何小部件工具包,如 GTK),或数据库句柄或连接...
- 等...
主要问题是 属性 而不是 leaking resources is a global whole-program property (or at least global to all functions possibly called between setjmp
and longjmp
), so it prohibits modular software development:任何其他同事必须在 setjmp
和 [=11] 之间的任何函数中改进某些代码=] 必须意识到该限制并遵守该纪律。
因此,如果您使用 setjmp
,请非常清楚地记录下来。
顺便说一句,如果你只关心malloc
,系统地使用 Boehm's conservative garbage collector会有很大帮助;您将在任何地方使用 GC_malloc
而不是 malloc
,并且您不会关心 free
,实际上这就足够了;那么您可以毫无顾虑地使用 setjmp
(因为您可以在 setjmp
和 longjmp
之间调用 GC_malloc
)。
(请注意,垃圾收集器的概念和术语与异常处理和setjmp
非常相关,但很多人对它们的了解还不够。阅读Garbage Collection Handbook应该是值得的)
另请参阅 RAII and learn about C++11 exceptions (and their relation to destructors). Learn a bit about continuations and CPS。
阅读 setjmp(3), longjmp(3) (and also about sigsetjmp
, siglongjmp
, and setcontext(3)) 并注意编译器必须了解 setjmp
您应该注意,在某些情况下调用 setjmp 并不能保证安全(例如,您不能可移植地存储 setjmp 的 return 值)。
此外,如果您想在同一函数中调用 setjmp 后访问局部变量,您应该将这些变量标记为 volatile。
使用 setjmp 和 longjmp 也很有用,因为如果递归导致堆栈溢出,您可以使用来自信号处理程序的 longjmp 恢复(不要忘记设置备用堆栈)和 return 错误反而。如果你想这样做,你应该考虑使用 sigsetjmp 和 siglongjmp 来保存信号配置。