`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 的函数之前。)

setjmplongjmp 可以看作是穷人的 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(因为您可以在 setjmplongjmp 之间调用 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 来保存信号配置。