KEEP K.I.S.S.

tk's blog

setjmp/longjmp 使用注意

  1. 不应从调用 setjmp() 的过程中返回。
    如果setjmp所在的函数已经调用返回了,那么longjmp 使用该处 setjmp 所填写的对应 jmp_buf 缓冲区将不再有效。这是因为longjmp 所要返回的"栈帧"(stack frame) 已经不再存在了,程序返回到一个不再存在的执行点,很可能覆盖或者弄坏程序栈。
    上篇文章叙述过这个,也可以参见:setjmp 的正确使用
     
  2. 保证局部变量在 longjmp 过程中一直保持它的值的唯一可靠方法是把它声明为 volatile (这使用于那些值在 setjmp 执行和longjmp 返回之间会改变的变量) 详细可以参见:setjmp 和 longjmp,以及对变量的影响 

    简单来说如下:
    1. 优化不影响 global, static, volatile 变量。
    2. 标出来的那句话意思是,保存在内存中的变量,longjmp返回后,保持了longjmp时的值。
        而在 cpu 中的值将会退回到setjmp时的值。
    3. 没有优化时global, static, volatile, auto, register都存在内存中。
        返回的时候,这些值仍然是longjmp时候的值。
        但在有优化的时候, auto, register的值是在寄存器的。
        所以返回的时候,这两个值是在setjmp时候的值。
     
  3. 编程的惯用法: if( setjmp(x) ){/* handle longjmp(x) */}。
     
  4. 仅在特定范围内引用 setjmp 。

setjmp 和 longjmp

本文主要讲解 C 库中的函数 setjmp 和 longjmp,也就是所谓的 非局部跳转。

本文主要翻译和出自 Jim Plank 的讲座 CS360 Lecture notes -- Setjmp

翻译 by tisyang 自我感觉不直观的翻译都在括号中附加了原文


setjmp()/longjmp()

Setjmp() 和 longjmp() 是在 C/Unix 下用于执行复杂控制流的子程序。

理解 setjmp() 和 longjmp() 的关键之一就在于理解 机器布局(machine layout), 这个在几周前的 汇编和内存分配(assembler and malloc) 讲座中描述过。 一个程序的状态完全取决于它内存中的内容(contents of its memory) (比如 代码(code), 全局变量(globals), 堆(heap), 和栈(stack)), 以及寄存器中的内容。 寄存器中的内容包括 栈指针寄存器(stack pointer,缩写 sp)、帧指针寄存器(frame pointer,指向栈中一个函数的local 变量的首地址,缩写 fp)和 程序计数器寄存器(program counter,缩写 pc)。 setjmp() 所做的事情就是保存当前这些寄存器的内容以便在以后的某个时刻 longjmp() 可以恢复它们。 因此,longjmp() 可以“返回” 到 setjmp()被调用时刻的程序的状态。

具体来看:

#include < setjmp.h >
int setjmp(jmp_buf env);

这是将当前寄存器的状态保存到 env 中。 如果打开 /usr/include/setjmp.h, 你将看到 jmp_buf 的定义如下:

#define _JBLEN  9
typedef struct { int _jb[_JBLEN + 1]; } jmp_buf[1];

这说明 jmp_buf 是一个包含数量为 _JBLEN+1 的整数数组。

因此, 当调用 setjmp() 时,传递的参数是这样一个整数数组的地址,接着函数把所有寄存器的值保存到这个数组之中。在这种情况下调用, setjmp() 会返回 0。

longjmp(jmp_buf env, int val);

Longjmp() 会将寄存器重置为保存在 env 中的值 ,这包括 spfp 以及 pc这意味着 longjmp() 函数不会返回。 相反, 当调用 longjmp() 时,程序流会返回到好像刚刚调用完 setjmp() (保存了 env )一样。 这是因为 pc(程序计数器)和其他寄存器的内容都被一起恢复了。此时,setjmp() 会返回传递给 longjmp() 的参数 val 的值,注意 val 的值不允许为 0 (请参阅 man 帮助系统)。因此,当 longjmp() 被调用时,setjmp() 会返回一个非0值, 程序流也从 setjmp() 中返回。

用一个示例来说明,看如下的代码 (in sj1.c):