【问题标题】:Is it good programming practice to use setjmp and longjmp in C?在 C 中使用 setjmp 和 longjmp 是一种好的编程习惯吗?
【发布时间】:2011-08-31 19:42:39
【问题描述】:

我是一名 C++ 程序员,习惯使用具有良好异常处理能力的 OO 语言。

据我了解,setjmp 和 longjmp 本质上是一种 c 风格的传播异常条件的方法。它们似乎也是一种强烈的 goto 形式,可以向上传播堆栈。

所以,首先:在这个时间点直接在 C 中使用这些是一种好习惯,还是被弃用? (注意:C 不是 C++)。

其次,它们在 C++ 中是否有用,或者我认为它们是被 C++ 的异常处理功能取代的遗留机制是否正确?

【问题讨论】:

  • 您不能在信号处理程序中抛出异常,但您可以安全地执行longjmp -- 只要您知道自己在做什么。
  • @Dietrich:对您的评论 +1。这是一个鲜为人知且完全被低估的事实。如果不使用信号处理程序中的longjmp,有许多问题无法解决(讨厌的竞争条件)。阻塞系统调用的异步中断就是典型的例子。

标签: c exception unix


【解决方案1】:

基本上,您的断言是正确的,jmp 风格的传播与goto 本质上是一样的。阅读Dijkstra's (famous and controversial) paper 关于gotos 的内容(我认为)为为什么很少使用gotos 提供了合理的推理。除非你确切地知道你为什么要做你正在做的事情(或者你在非常特定的领域工作——比如嵌入式编程),否则你不应该接触gotolongjmp

【讨论】:

  • +1 表示“除非你确切地知道你为什么要做你正在做的事情”。我唯一一次不得不使用setjmp/longjmp 是在使用libpng 库时——当libpng 在解码PNG 文件时遇到错误,它希望longjmp 回到你的代码,所以你必须调用setjmp正确使用它。
  • 哇,这似乎是一个非常愚蠢的设计。相反,它可以被写入longjmp 回到 libpng 本身的入口点,并简单地将错误代码返回给调用者......
  • 需要注意的一点,我认为这可能是所有 C 语言中最晦涩难懂的一点:如果一个函数设置一个本地 var,然后调用 setjmp,然后更改该 var,然后调用一个函数其中 longjmp 会返回到 setjmp,那么本地 var 可能会或可能不会恢复到 setjmp 时的值。基本上它取决于该值是否在 setjmp 的被调用者保存的 reg 中。 (和“C 中最晦涩的部分”,因为 setjmp 不完全是一个 lib 函数,编译器在为调用它的函数生成代码时通常需要识别它)。
  • @greggo 这就是标准规定在这种情况下使用 volatile 变量的原因,从而避免了您描述的未定义行为。
【解决方案2】:

它们用于实现协程。网上有几个 c++ 协程库在运行,在 Unix/Linux 中将使用 setjmp/longjmp 来实现功能。

因此,如果您的目标是实现一个协程库,那么它是否是一种良好的做法是一个有争议的问题,因为在这些平台上,它是支持该功能的唯一方法

如果您的目标是使用协程库,则应该搜索其中的一些。甚至还有一个名为 boost::context 的 boost Vault 提案,该提案已经获得批准。

【讨论】:

  • POSIX 系统上的协程库可能使用set_context/make_context,因为您无法使用setjmp/longjmp 维护多个堆栈。
  • @Alexandre C. 你的意思可能是setcontextmakecontext。这些已被宣布为过时。
  • @Jens:是的。他们是吗?我记得曾经用它们来实现协程,因为makecontext 可以用来调用带有自定义堆栈的函数。赞成他们会被弃用什么?
  • @Jens:哦,我明白了,(stackoverflow.com/questions/4298986/…)。我认为使用它们没有任何问题。
  • @R.:它们解决的问题与线程解决的问题完全不同。如果我想要一堆小堆栈,它们的函数以可预测的方式相互让步(比如生成器/消费者,或类似边缘的树算法),那么我想要协程,而不是线程。
【解决方案3】:

setjmp/longjmp 有一些正确用法。用它们实现协程几乎是不可能的,因为您必须使用(不可移植的)技巧(阅读:内联汇编)来切换堆栈。

setjmp/longjmp 的一个用途是捕获浮点信号,但这会扰乱 C++ 堆栈展开。不过在 C 中是正确的。

您还可以实现某种形式的堆栈展开(通过维护您自己的清理处理程序堆栈)并使用它们在 C 中实现真正的析构函数和异常。这在大型项目中非常方便:缺乏正确的错误处理机制是 C 的弱点。但是,要正确地做到这一点非常困难,你必须编写一堆宏来促进任务。

【讨论】:

  • 实际上你可以在没有内联汇编的情况下切换堆栈,使用sigaltstacksigactionraise。 :-)
  • 这可能很慢,而且所需的内联汇编的数量并不是很大。您应该能够为每个目标处理器创建不同的 asm。
【解决方案4】:

您当然不想在 C++ 中使用 setjmp,正如您所说的,这就是例外的用途。您也不想在 C 中使用它们,因为要正确使用它们非常困难。努力寻找其他解决方案。

【讨论】:

    【解决方案5】:

    setjmp/longjmp 是在纯 C 中实现自己的异常处理的有用方法。http://sourceware.org/pthreads-win32/announcement.html

    【讨论】:

      【解决方案6】:

      在异常处理(Try+catch+finally+..)流行(使用 longjmp)之前,您应该提出 goto 和 longjmp 不好的问题。如果人们无法处理 goto 的稀疏使用以使事情变得更容易,那么他们如何处理 longjmp 异常处理绕过的所有逻辑排列,然后像什么都没发生一样继续?真正的问题是人们在寻找规则而不是概念。

      【讨论】:

        【解决方案7】:

        setjmplongjmp 是用于绕过正常函数调用和返回流程的宏。
        setjmp 保存调用环境以供 longjmp 使用
        正确使用这些宏非常困难,而且您很容易以未定义的行为告终。
        因此,例如强制将longjmp 限制为1 级信号处理程序(最好实际上根本不被调用)。
        在关键系统中,要求根本不使用它。

        【讨论】:

        • longjmp 在哪里限制为一级信号处理程序? longjmp 是异步信号不安全的,因此如果中断的代码(在主上下文或先前调用的信号处理程序上下文中)可能正在使用异步信号不安全函数,则不能从信号处理程序调用它,但除此之外我不知道有这样的限制。
        • 我记得在某处读到与官方标准有关的地方,对 longjmp 的限制是一个错误,将被删除。
        【解决方案8】:

        除了到目前为止发布的答案之外,我想补充一点,C 中的 setjmp 允许以优雅的方式实现尾递归。

        所以是的,这不仅是一个好习惯,而且是在 C 中做某些事情的唯一可移植的优雅方式。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-09-18
          • 1970-01-01
          • 2019-03-11
          • 1970-01-01
          • 1970-01-01
          • 2013-08-03
          • 1970-01-01
          • 2011-03-02
          相关资源
          最近更新 更多