| C標準函式庫 |
| --- |
| 
一般 |
| * 数据类型
* 字符类型
* 字符串
* 数学
* 文件输入/输出
* 日期/时间
* 本地化
* 内存分配
* 过程控制
* 信号
* 替代标记
 |
| 
杂项 |
| * `<assert.h>`
* `<errno.h>`
* `<setjmp.h>`
* `<stdarg.h>`
 |
| * 
* 论
* 编
 |

 

**setjmp.h**是C标准函数库中提供"非本地跳转"的头文件：控制流偏离了通常的子程序调用与返回序列。互补的两个函数setjmp与longjmp提供了这种功能。

setjmp/longjmp的典型用途是异常处理机制的实现：利用longjmp恢复程序或线程的状态，甚至可以跳过栈中多层的函数调用。

成员函数
----

|  |  |
| --- | --- |
| `int setjmp(jmp_buf env)` | 建立本地的`jmp_buf`缓冲区并且初始化，用于将来跳转回此处。这个子程序 保存程序的调用环境于`env`参数所指的缓冲区，`env`将被`longjmp`使用。如果是从`setjmp`直接调用返回，`setjmp`返回值为0。如果是从`longjmp`恢复的程序调用环境返回，`setjmp`返回非零值。 |
| `void longjmp(jmp_buf env, int value)` | 恢复`env`所指的缓冲区中的程序调用环境上下文，`env`所指缓冲区的内容是由`setjmp`子程序调用所保存。`value`的值从`longjmp`传递给`setjmp`。`longjmp`完成后，程序从对应的`setjmp`调用处继续执行，如同`setjmp`调用刚刚完成。如果`value`传递给`longjmp`零值，`setjmp`的返回值为1；否则，`setjmp`的返回值为`value`。 |
|

`setjmp`保存当前的环境（即程序的状态）到平台相关的一个数据结构 (`jmp_buf`)，该数据结构在随后程序执行的某一点可被 `longjmp`用于恢复程序的状态到`setjmp`调用所保存到`jmp_buf`时的原样。这一过程可以认为是"跳转"回`setjmp`所保存的程序执行状态。`setjmp`的返回值指出控制是正常到达该点还是通过调用`longjmp`恢复到该点。因此有编程的惯用法: `if( setjmp(x) ){/* handle longjmp(x) */}`。

成员类型
----

|  |  |
| --- | --- |
| `jmp_buf` | 数组类型，例如`struct int[16]`或`struct __jmp_buf_tag`，用于保存恢复调用环境所需的信息。 |

告诫与限制
-----

`longjmp`实现了非本地跳转，微软的IA32程序设计环境中正常的"栈卷回"("stack unwinding")因而没有发生，所以诸如栈中已定义的局部变量的析构函数的调用（用于销毁该局部变量）都没有执行。所有依赖于栈卷回调用析构函数所做的扫尾工作，如关闭文件、释放堆内存块等都没有做。但在微软的X64程序设计环境，`longjmp`启动了正常的"栈卷回"。

如果`setjmp`所在的函数已经调用返回了，那么`longjmp`使用该处`setjmp`所填写的对应`jmp_buf`缓冲区将不再有效。这是因为`longjmp`所要返回的"栈帧"(stack frame)已经不再存在了，程序返回到一个不再存在的执行点，很可能覆盖或者弄坏程序栈.

使用例子
----

### 简单例子

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

static jmp\_buf buf;

void second(void) {
 printf("second\n"); // 打印
 longjmp(buf,1); // 跳回setjmp的调用处 - 使得setjmp返回值为1
}

void first(void) {
 second();
 printf("first\n"); // 不可能执行到此行
}

int main() { 
 if ( ! setjmp(buf) ) {
 first(); // 进入此行前，setjmp返回0
 } else { // 当longjmp跳转回，setjmp返回1，因此进入此行
 printf("main\n"); // 打印
 }

 return 0;
}

```

上述程序将输出: 

```
second
main
```

注意到虽然`first()`子程序被调用，"`first`"不可能被打印。"`main`"被打印，因为条件语句`if ( ! setjmp(buf) )`被执行第二次。

### 异常处理

在下例中，`setjmp`被用于包住一个例外处理，类似`try`。`longjmp`调用类似于`throw`语句，允许一个异常返回给`setjmp`一个异常值。下属代码示例遵从1999 ISO C standard与Single UNIX Specification：仅在特定范围内引用`setjmp`

* `if`，`switch`或它们的嵌套使用的条件表达式
* 上述情况下与`!`一起使用或者与整数常值比较
* 作为单独的语句(不使用其返回值)

遵从上述规则使得创建程序环境缓冲区更为容易。更一般的使用`setjmp`可能引起未定义行为，如破坏局部变量；编译器被要求保护或警告这些用法。但轻微的复杂用法如`switch ((exception_type = setjmp(env))) { }`在文献与实践中是常见的，并保持了相当的可移植性。

```
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
 
void first(void);
void second(void);
 
/\* This program's output is:
 
calling first
calling second
entering second
second failed with type 3 exception; remapping to type 1.
first failed, exception type 1
 
\*/
 
/\* Use a file scoped static variable for the exception stack so we can access
 \* it anywhere within this translation unit. \*/
static jmp\_buf exception\_env;
static int exception\_type;
 
int main() {
 void \*volatile mem\_buffer;
 
 mem\_buffer = NULL;
 if (setjmp(exception\_env)) {
 /\* if we get here there was an exception \*/
 printf("first failed, exception type %d\n", exception\_type);
 } else {
 /\* Run code that may signal failure via longjmp. \*/
 printf("calling first\n");
 first();
 mem\_buffer = malloc(300); /\* allocate a resource \*/
 printf("%s",strcpy((char\*) mem\_buffer, "first succeeded!")); /\* ... this will not happen \*/
 }
 if (mem\_buffer)
 free((void\*) mem\_buffer); /\* carefully deallocate resource \*/
 return 0;
}
 
void first(void) {
 jmp\_buf my\_env;
 
 printf("calling second\n");
 memcpy(my\_env, exception\_env, sizeof(jmp\_buf));
 switch (setjmp(exception\_env)) {
 case 3:
 /\* if we get here there was an exception. \*/
 printf("second failed with type 3 exception; remapping to type 1.\n");
 exception\_type = 1;

 default: /\* fall through \*/
 memcpy(exception\_env, my\_env, sizeof(jmp\_buf)); /\* restore exception stack \*/
 longjmp(exception\_env, exception\_type); /\* continue handling the exception \*/

 case 0:
 /\* normal, desired operation \*/
 second();
 printf("second succeeded\n"); /\* not reached \*/
 }
 memcpy(exception\_env, my\_env, sizeof(jmp\_buf)); /\* restore exception stack \*/
}
 
void second(void) {
 printf("entering second\n" ); /\* reached \*/
 exception\_type = 3;
 longjmp(exception\_env, exception\_type); /\* declare that the program has failed \*/
 printf("leaving second\n"); /\* not reached \*/
}

```

用于信号处理
------

在信号处理机制中，进程在检查收到的信号，会从原来的系统调用中直接返回，而不是等到该调用完成。这种进程突然改变其上下文的情况，就是通过使用setjmp和longjmp来实现的。setjmp将保存的上下文载入用户空间，并继续在旧的上下文中继续执行。这就是说，进程执行一个系统调用，当因为资源或其他原因要去睡眠时，内核为进程作了一次setjmp，如果在睡眠中被信号唤醒，进程不能再进入睡眠时，内核为进程调用longjmp，该操作是内核为进程将现在的上下文切换成原先通过setjmp调用保存在进程用户区的上下文，这样就使得进程可以恢复等待资源前的状态，而且内核为setjmp返回1，使得进程知道该次系统调用失败。

参考文献
----

1. 1 2  ISO C标准要求`setjmp`必须是宏实现，但POSIX明确称未定义`setjmp`是宏实现还是函数实现。
2. ↑  Visual Studio 2008用法
3. ↑  GNU C 函式庫 2.7的用法
4. ↑  Microsoft Visual C++ 2010 x32或x64与Intel ICC 2011 (version 12) x32或x64，编译结果都是`longjmp`启动了正常的"栈卷回"。但GCC 4.4 x32版编译的`longjmp`不执行"栈卷回"。可见，是否"栈卷回"不具有移植性。
5. ↑ .  [2011-06-06]. （原始内容存档于2010-07-04）.
6. ↑ .  [2011-06-06]. （原始内容存档于2009-07-26）.

外部链接
----

* Exceptions in C with Longjmp and Setjmp（页面存档备份，存于）