面试题答案
一键面试setjmp 和 longjmp 函数工作原理
setjmp
setjmp
函数用于保存当前调用环境的上下文,以便后续 longjmp
可以恢复到此位置继续执行。它在调用点记录寄存器的值、栈指针等信息到一个 jmp_buf
类型的缓冲区中。
int setjmp(jmp_buf env);
env
是一个jmp_buf
类型的数组,用于保存上下文环境。- 返回值:首次调用
setjmp
时返回0
,通过longjmp
跳回时返回非零值。
longjmp
longjmp
函数用于恢复由 setjmp
保存的上下文环境,使程序跳回到 setjmp
调用处继续执行。
void longjmp(jmp_buf env, int val);
env
是之前由setjmp
填充的jmp_buf
缓冲区。val
是传递给setjmp
的返回值,setjmp
会以这个值作为返回值,除非val
为0
,此时setjmp
将返回1
。
保存和恢复执行环境
- 保存:
setjmp
保存当前栈指针、通用寄存器等执行环境到jmp_buf
中。这使得程序状态被记录下来。 - 恢复:
longjmp
从jmp_buf
中取出保存的执行环境,恢复栈指针和寄存器的值,程序继续从setjmp
调用处执行,就好像函数调用链被“重置”到了setjmp
调用点。
使用陷阱及避免方法
陷阱一:局部变量的状态问题
- 问题描述:当使用
longjmp
跳回时,局部变量的值可能不是预期的。因为longjmp
恢复的是setjmp
调用时的栈状态,自setjmp
调用后修改的局部变量不会被恢复。 - 避免方法:将需要在
longjmp
后保持状态的变量声明为volatile
或使用全局变量。 - 代码示例:
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void second() {
int a = 5;
// 如果没有 volatile,a 的值可能不是预期的 10
volatile int b = 10;
longjmp(env, 1);
a = 7; // 此语句不会执行到
b = 12; // 此语句不会执行到
}
void first() {
if (setjmp(env) == 0) {
second();
} else {
// 这里 a 可能仍为 5,因为 longjmp 不会恢复自 setjmp 调用后修改的局部变量
// 但 b 会是 10,因为 b 是 volatile
printf("After longjmp: a = %d, b = %d\n", a, b);
}
}
int main() {
first();
return 0;
}
陷阱二:栈展开和资源清理
- 问题描述:
longjmp
不会像正常函数返回那样进行栈展开,因此可能导致资源(如打开的文件、分配的内存)没有正确清理。 - 避免方法:在
setjmp
之前手动管理资源的清理,或者使用atexit
注册清理函数。 - 代码示例:
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#include <unistd.h>
jmp_buf env;
FILE* file;
void cleanup() {
if (file) {
fclose(file);
}
}
void second() {
file = fopen("test.txt", "w");
if (!file) {
longjmp(env, 1);
}
// 文件操作
fclose(file);
}
void first() {
atexit(cleanup);
if (setjmp(env) == 0) {
second();
} else {
printf("File open failed, exiting.\n");
}
}
int main() {
first();
return 0;
}