在本地 jmp_buf 中使用 setjmp 和 longjmp

Using setjmp and longjmp with a local jmp_buf

如果一个localjmp_buf实际上是用寄存器而不是栈内存来表示的,setjmp或者longjmp有没有可能导致local[=]的内容13=] 在 setjmp returns 来自 longjmp?

时不确定

建议重复 在全局变量的上下文中询问。有人建议,因为答案解释了变量没有以阻止它随后被调用的方式修改,这也充分回答了局部变量的问题。
但是,局部变量的处理方式不同于全局变量。特别是,如果本地 jmp_buf 变量实际上保存在寄存器中而不是内存中,则 longjmp 之后的恢复可能不会呈现可重用的 jmp_buf 变量。


作为一项学术练习,我尝试使用 setjmp 代替 goto。为了将循环替换保留在函数本地,使用的 jmp_buf 也是一个局部变量。

void foo (int n) {
    jmp_buf jb;
    volatile int i;
    i = setjmp(jb);
    if (i < n) {
        do_stuff(i);
        longjmp(jb, ++i);
    }
}

我了解在 setjmp 调用和 longjmp 调用之间修改的非易失性局部变量在 longjmp 之后未指定。但是,我对局部 jmp_buf 变量本身很好奇,特别是在 jmp_buf 变量由寄存器而不是堆栈上的内存表示的情况下。

不清楚 longjmp 本身是否可以被认为是可以修改局部 jmp_buf 变量的东西,以及这是否意味着它的内容在 setjmp returns 时未指定在调用 longjmp.

之后

我以为我可以通过将 jb 声明为 volatile 来轻松解决问题,但这触发了警告(我将其视为错误):

... error: passing argument 1 of ‘_setjmp’ discards ‘volatile’ qualifier from pointer target type [-Werror=discarded-qualifiers]
     setjmp(jb);
            ^~

此外,setjmp 的规范并没有说明它是在设置 jmp_buf 之后还是在设置 jmp_buf 之前保存寄存器值。

如果我需要关心它,我可以创建一个 jmp_buf 的可变副本并复制它的内容。但是,如果不需要,我想避免这种情况。

没关系。

在相关说明中,您不需要 i 上的 volatile,因为它是由 setjmp() 分配给的。

仔细阅读 longjmp() 的手册页和我的 K&R C 副本,jb 的内容仅在您的函数体内无效,这意味着如果有第二次调用 longjmp(),它将看到 jb 的有效视图。在合理的假设下,有效代码不会在较新的标准版本中变得无效,这在今天仍然适用。

TL;DR 你不需要标记 jmp_buf volatile 类型的变量。

C11 standard 节 §7.13.2.1 第 3 点指出:

All accessible objects have values, and all other components of the abstract machine have state, as of the time the longjmp function was called, except that the values of objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate.

您的 jmp_buf 对象在 setjmp(jb)longjmp(jb, ++i) 之间没有变化。调用之间唯一改变的变量是 i,按照标准建议声明为 volatile

所以,为了回答你的问题,longjmp 不能单独“修改本地 jmp_buf 的内容 [以这种方式] 会导致其内容在 [=11 时未定义=] returns”,但是通过其他方式修改两次调用之间的jmp_buf肯定会造成麻烦。

TL;DR 由于标准不明确,最好把a的值local jmp_buf 在 local longjmp.

之后不确定

ISO/IEC 9899:2018 §17.13.1.1 ¶2 描述了 setjmp 的行为,而 ¶3 描述了 return.

上发生的事情

The setjmp macro saves its calling environment in its jmp_buf argument for later use by the longjmp function.

...

If the return is from a direct invocation, the setjmp macro returns the value zero. If the return is from a call to the longjmp function, the setjmp macro returns a nonzero value.

我们推断 setjmp 的成功 return 会导致初始化的 jmp_buf 参数。但是,没有提及初始化是否考虑到 jmp_buf 本身具有自动存储持续时间(因此,它本身可以由寄存器而不是内存表示)。

ISO/IEC 9899:2018 §7.13.2.1 ¶3 描述了 longjmp 的行为,措辞与 引用的 2011 年文本相同:

All accessible objects have values, and all other components of the abstract machine254) have state, as of the time the longjmp function was called, except that the values of objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate.


254)This includes, but is not limited to, the floating-point status flags and the state of open files.

不过,between这个词的意思有点难以捉摸。该标准可以明确指定 between 的上下文,表示在 setjmp 完成之后。例如,措辞可以是:

... changed between the setjmp return and longjmp call are indeterminate.

当前的措辞表明,应该将 setjmp 本身的调用包括在内,作为可能触发不确定条件的东西。

但是,longjmp 的 return 的语义有可能涵盖这个问题。 ISO/IEC 9899:2018 §17.13.2.1 ¶4 状态:

After longjmp is completed, thread execution continues as if the corresponding invocation of the setjmp macro had just returned the value specified by val. ...

这句话可以解释为 setjmp 的调用语义是相同的,无论是来自直接调用的 return 还是来自 longjmp 的 return功能。也就是说,setjmp 的 return 意味着 jmp_buf 参数被初始化并且可以被另一个 longjmp 使用。但同样,这还不清楚。在最有限的解释中,as if 子句仅表示由 setjmp 编辑的值 return,而不是调用本身。

由于语义不明确,根据 longjmp.

jmp_buf 对象值视为不确定 return 是正确的