KEEP K.I.S.S.

tk's blog

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):