`setjmp`和`longjmp`的安全使用

6

我知道人们总是说不要使用longjmp,它很邪恶,很危险。

但我认为它可以用于退出深层递归/嵌套函数调用。

单个longjmp是否比重复的检查和返回(如if(returnVal != SUCCESS) return returnVal;)更快?

至于安全性,只要动态内存和其他资源被正确释放,就不应该有问题,对吧?

到目前为止,使用longjmp似乎并不困难,而且它甚至使我的代码更简洁。我很想多次使用它。

(在许多情况下,在深度递归中根本没有分配动态内存/资源。 深层函数调用似乎更常见于数据解析/操作/验证。 动态分配通常发生在高级别之前,在出现setjmp的函数调用之前。)


3
只要你意识到可能会产生的问题并且愿意冒“意大利面代码”的风险,它在某些情况下是有用的。 - Some programmer dude
“看起来使用longjmp并不困难,而且它甚至可以使我的代码更加简洁”,但这样做会导致更复杂的代码,更难以维护。 - alk
很可能是一个重复问题,链接为:https://dev59.com/w3RA5IYBdhLWcg3wzhRY。 - alk
2个回答

7

setjmplongjmp可以被视为穷人版的异常机制。顺便说一下,Ocaml的异常与setjmp一样快,但语义更加清晰。

当然,在中间函数中重复返回错误代码比longjmp要慢得多,因为它会弹出可能很大的调用堆栈部分。

(我隐含地关注Linux)

只要在它们之间没有分配任何资源,包括:

  • 堆内存(malloc
  • fopen打开的FILE*句柄
  • 打开操作系统文件描述符(例如用于套接字)
  • 其他操作系统资源,如定时器或信号处理程序
  • 获取由某些服务器管理的某些外部资源,例如 X11 窗口(因此使用任何小部件工具包如 GTK),或数据库句柄或连接...
  • 等等...

主要问题在于,泄漏资源的属性是全局整个程序的属性(或至少是所有在setjmplongjmp之间可能被调用的函数的全局属性),因此它禁止模块化软件开发:任何其他同事必须在意这种限制并遵循该纪律以改进setjmplongjmp 之间的任何函数中的一些代码。

因此,如果您使用 setjmp,请务必清楚地记录下来。
顺便提一下,如果您只关心 malloc,系统地使用 Boehm 的保守垃圾收集器会有很大帮助;您将在所有地方使用 GC_malloc 而不是 malloc,您将不再关心 free,并且实际上这已经足够了;然后您可以放心使用 setjmp(因为您可以在 setjmp 和 longjmp 之间调用 GC_malloc)。
请注意,垃圾收集器周围的概念和术语与异常处理和 setjmp 相关联,但许多人对它们了解不够。阅读《垃圾收集手册》应该是值得的。

阅读有关 RAII 的内容,并了解 C++11 异常(以及它们与 析构函数 的关系)。学习一些关于 continuationsCPS 的知识。

阅读 setjmp(3)longjmp(3)(还要了解 sigsetjmpsiglongjmpsetcontext(3)),请注意编译器需要知道 setjmp


3
请注意,在某些情况下调用setjmp可能不安全(例如,您无法可移植地存储setjmp的返回值)。
此外,如果您想在调用setjmp后在同一函数中访问可能已更改的本地变量,应将这些变量标记为volatile。
使用setjmp和longjmp也很有用,因为如果递归导致堆栈溢出,您可以通过信号处理程序中的longjmp进行恢复(不要忘记设置备用堆栈),并返回错误。如果要执行此操作,请考虑使用sigsetjmp和siglongjmp以保留信号状态。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接