【问题标题】:About setjmp/longjmp关于 setjmp/longjmp
【发布时间】:2011-11-01 15:52:28
【问题描述】:

我在调查setjmp/longjmp,发现setjmp保存了指令指针、堆栈指针等寄存器...

但是我在这里不明白的是,线程本身堆栈中的数据不能在调用 setjmplongjmp 之间进行修改。在这种情况下,longjmp 不会按预期工作。

为了说清楚,例如当longjmp恢复堆栈指针时,假设堆栈指针现在指向的内存中的数据与setjmp时不一样strong> 被调用。这会发生吗?如果发生这种情况,我们不是有麻烦吗?

还有这句话的意思,“在调用 setjmp() 例程的例程返回后,不能调用 longjmp() 例程。

【问题讨论】:

  • 很像“分配局部变量的例程返回后,不能使用局部变量”。调用setjmp() 时处于作用域内的堆栈变量在调用longjmp() 时必须仍在作用域内。

标签: c linux x86 setjmp


【解决方案1】:

堆栈指针标记堆栈的“已使用”和“未使用”部分之间的划分。当您调用setjmp 时,所有当前调用帧都在“已使用”端,并且在setjmp 之后但在调用setjmp 的函数返回之前发生的任何调用,都将它们的调用帧放在“未使用”端保存的堆栈指针的一侧。请注意,在调用setjmp 的函数返回后调用longjmp 会调用未定义的行为,因此不需要考虑这种情况。

现在,在setjmp 之后,可能会通过调用函数或通过指针修改某些现有调用帧中的局部变量,这就是在许多情况下必须使用volatile 的原因之一。 ..

【讨论】:

  • 感谢您的回答,但您能解释一下“在调用 setjmp 的函数返回后调用 longjmp”是什么意思吗?在这种情况下应该在哪里使用 volatile 。我的意思是哪些变量。
  • 他的意思是从一个函数“外部”调用它到setjmp调用。
  • @MetallicPriest:在 Wikipedia page on continuations 中进行了解释:一种更有限的类型是可用于将当前上下文转义到周围上下文的转义延续。许多不明确支持延续的语言都支持异常处理,这相当于转义延续,可以用于相同的目的。 C 的 setjmp/longjmp 也是等价的:它们只能用于展开堆栈。转义延续也可用于实现尾调用优化
  • @ninjali,你的意思是例如这个...... void somefunction() { setjmp(); } int main() { somefunction(); longjmp(...) } 不正确?
  • getcontext/setcontext 也不适用。您必须使用 makecontext... 创建一个完全独立的堆栈
【解决方案2】:

setjmp()/longjmp() 并不是为了保存堆栈,这就是setcontext()/getcontext() 的用途。

该标准规定,在调用setjmp() 的函数中定义的非易失性自动变量的值在setjmp()longjmp() 调用之间更改,在longjmp() 之后未指定。出于同样的原因,您如何调用setjmp() 也有一些限制。

【讨论】:

  • 这个问题的答案 (stackoverflow.com/questions/15115480/…) 说setcontext()/getcontext() 不保存堆栈。 setcontext()/getcontext()setjmp()/longjmp() 之间有什么区别?
  • @apple16: setjmp()/longjmp() 旨在展开堆栈。 getcontext()/setcontext()/makecontext()/swapcontext() 旨在在协程上下文(每个都有自己的堆栈)之间切换,其中一些是由makecontext() 创建的。
【解决方案3】:

C 中的 setjmp/longjmp(以下称为 slj)特性很丑陋,其行为可能因实现而异。尽管如此,鉴于没有异常,在 C 中有时需要 slj(请注意,C++ 提供的异常几乎在所有方面都优于 slj,而且 slj 与许多 C++ 特性的交互很差)。

在使用 slj 时,应牢记以下几点,假设例程 Parent() 调用例程 Setter(),Setter() 调用 setjmp(),然后调用 Jumper,后者又调用 longjmp()。

  1. 代码可以合法地退出执行 setjmp 而不执行 longjmp 的范围;但是,一旦范围退出,之前创建的 jmp_buf 必须被视为无效。编译器可能不会做任何事情来标记它,但是任何使用它的尝试都可能导致不可预知的行为,可能包括跳转到任意地址。
  2. Jumper() 中的任何局部变量都会随着对 longjmp() 的调用而消失,从而使它们的值无关紧要。
  3. 每当控制权通过任何方式返回给 Parent 时,Parent 的局部变量将与调用 Setter 时一样,除非此类变量的地址已被占用并使用此类指针进行了更改;无论如何,setjmp/longjmp 不会以任何方式影响它们的值。如果这些变量的地址没有被占用,setjmp() 可能会缓存这些变量的值,而 longjmp() 可能会恢复它们。但是,在这种情况下,变量将无法在缓存和恢复之间发生变化,因此缓存/恢复不会产生明显的影响。
  4. Setter 中的变量可能会或可能不会被 setjmp() 调用缓存。在 longjmp() 调用之后,这些变量可能具有它们在执行 setjmp() 时具有的值,或者它们在调用最终调用 longjmp() 的例程时具有的值,或者它们的任何组合。至少在某些 C 方言中,此类变量可能被声明为“易失性”以防止它们被缓存。

虽然 setjmp/longjmp() 有时很有用,但它们也可能非常危险。在大多数情况下,没有导致未定义行为的保护错误代码,并且在许多实际场景中,不正确的使用可能会导致坏事发生(不像某些类型的未定义行为,实际结果可能经常与程序员打算)。

【讨论】:

  • (4) 是错误的。标准要求使用volatile,实际上这是标准要求volatile 关键字具有的唯一效果,该标准可通过严格符合的程序进行测试。修改局部变量并在longjmp之后访问,如果没有使用volatile,会导致UB。
  • 我也很困惑你所说的“它的行为可能因实现而异”是什么意思。定义的行为根本没有变化。只是有很多 UB 案例需要考虑,但如果您了解自己在做什么,它们都是完全显而易见的......
  • @R:我说“至少有一些方言”。我不知道在最初的 K&R C 标准或第一个 ANSI 标准中是否指定了这种行为,或者是否在稍后的某个时间首次指定,但在“至少某些”方言中肯定是正确的。至于 UB,我不知道在执行 setjmp 的范围内更改局部变量是否会使生成的 jmp_buf 无效,或者它是否仅导致修改后的变量的值变为未定义(这样在写入之前读取将是未定义的行为),或者什么。
  • @R:也许我应该更清楚地说,除了在 longjmp() 调用后列出的可能变量内容的可能性之外,变量还可能包含鼻恶魔。在实践中,我认为它们可能包含新旧数据的任何组合这一事实足以阻止使用,即使它们不能包含鼻恶魔。至于实现之间的变化,我正在考虑调用 setjmp() 的范围内的变量之类的东西。一些实现让人们在没有“volatile”限定符的情况下逃脱,并且这种实现的用户......
  • @R: ...可能多年来一直忽略它们,却从未意识到它们依赖于未定义的行为。
【解决方案4】:

在下面的示例中,setjmp / longjump 通过指针更改位于 main 中的 i 的值。 I 永远不会在 for 循环中递增。如需更多乐趣,请参阅 albert.c 条目,http://www.ioccc.org/years-spoiler.html 1992 年 IOCCC 获胜者。 (我曾经 ROTFLed 阅读 C 源代码的少数几次之一......)

#include <stdio.h>
#include <setjmp.h>

jmp_buf the_state;

void helper(int *p);
int main (void)
{
int i;

for (i =0; i < 10;    ) {
    switch (setjmp (the_state) ) {
    case 0:  helper (&i) ; break;
    case 1:    printf( "Even=\t"); break;
    case 2:    printf( "Odd=\t"); break;
    default: printf( "Oops=\t"); break;
        }
    printf( "I=%d\n", i);
    }

return 0;
}
void helper(int *p)
{
*p += 1;
longjmp(the_state, 1+ *p%2);
}

【讨论】:

  • i 必须是 volatile 才能使此程序有效。
  • 可能是。但是有一个 un noalias-sed 指向它的指针浮动。可惜dmr已经死了……
  • 编译器可以看到 i 在情况 1 和 2 中未被修改,在这些情况下将 printf 移动到主体内,并重用 i 的寄存器缓存值循环测试...如果编译器在这里特别处理setjmp会很好,但我对使用volatile的要求的理解是它在那里,所以编译器不必对setjmp很聪明(因为一些极端情况非常难以解决,编译器无法做到;它们可能需要解决停机问题。
  • 顺便说一句:该程序不是为了提供答案,而是为了引发讨论。无论哪种情况,它都表明 main() 的自动变量(为了取悦语言律师,我避免使用“堆栈框架”一词) 可以在 setjmp 和 longjmp 之间进行更改。 Volatile 的语义是相当模糊的 IIRC。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 2020-08-21
  • 2016-03-22
相关资源
最近更新 更多