`

非局部跳转函数 setjmp 和 longjmp 介绍

阅读更多
    C 语言中的 goto 语句是不能跨越函数的,属于局部性跳转,若要执行跨函数跳转,可使用 setjmp 和 longjmp 函数,它们对于处理发生在很深层嵌套函数调用中的出错情况时非常有用。
#include <setjmp.h>
int setjmp(jmp_buf env);
                    /* 返回值:若直接调用,返回 0;若从 longjmp 返回,则为非 0 */
void longjmp(jmp_buf env, int val);

    setjmp 的参数 env 是一个特殊类型 jmp_buf,这种类型是某种形式的数组,其中存放了在调用 longjmp 时能用来恢复栈状态的所有信息。因为需要在另一函数中引用 env 变量,所以通常将 env 定义为全局变量。
    longjmp 的第一个参数就是在调用 setjmp 时所用的 env,第二个 val 应是一个非 0 值,它将成为从 setjmp 处返回的值。使用第二个参数是因为对于一个 setjmp 可以有多个 longjmp,因此通过测试返回值就可判断造成返回的 longjmp 是位于哪个函数。
    想象一下有这样的几个函数调用:main 函数调用 do_line 函数,do_line 函数又调用一个 cmd_add 函数,下图显示了调用 cmd_add 之后栈通常的大致使用情况(虽然栈并不一定要像低地址方向扩充,例如在某些没有对栈提供特殊硬件支持的系统上,栈帧可能是用链表实现的,但这是一种典型的栈安排)。

    如果某个时候在 cmd_add 函数中遇到了一个非致命性的错误,那可能不得不以检查返回值的方法逐层返回到 main,这可能会变得很麻烦。而如果利用 setjmp 和 longjmp 函数,就可在栈上跳过若干调用帧,直接返回到当前函数调用路径上的某一个函数中。所以如果在 main 中预先使用 setjmp 设置了跳转标记,那么当在 cmd_add 中遇到非致命性的错误时,就可调用 longjmp 使栈反绕到执行 main 函数时的情况,也就是抛弃了 cmd_add 和 do_line 的栈帧,如同下图所示。

    但接下来的问题是:当 longjmp 返回到 main 函数时,其中的自动变量和寄存器变量等的值是否能恢复到以前调用 setjmp 时的值?遗憾的是,对此问题的回答是“看情况”。大多数实现并不回滚这些自动变量和寄存器变量的值,而所有标准则称它们的值是不确定的。如果你有一个自动变量,而又不想使其值回滚,则可定义其为具有 volatile 属性。声明为全局变量和静态变量的值在调用 longjmp 时保持不变。
    下面这个程序说明了在调用 longjmp 后各种类型的变量的变化情况。
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static void f1(int, int, int, int);
static void f2(void);

static jmp_buf	jmpbuffer;
int	exteval;
static int	globval;

int main(void){
	int		autoval = 2;
	register int	regival	= 3;
	volatile int	volaval	= 4;
	static int	statval = 5;

	exteval = 0;
	globval = 1;

	if(setjmp(jmpbuffer) != 0){
		printf("after longjmp:\n");
		printf("exteval=%d, global=%d, autoval=%d, regival=%d, "
				"volaval=%d, statval=%d\n",
				exteval, globval, autoval, regival, volaval, statval);
		exit(0);
	}
	// Change variables after setjmp, buf before longjmp.
	exteval = 94; globval = 95; autoval = 96;
	regival = 97; volaval = 98; statval = 99;

	f1(autoval, regival, volaval, statval);	// never returns
	exit(0);
}

static void f1(int i, int j, int k, int l){
	printf("in f1():\n");
	printf("exteval=%d, global=%d, autoval=%d, regival=%d, "
			"volaval=%d, statval=%d\n",
			exteval, globval, i, j, k, l);
	f2();
}

static void f2(void){
	longjmp(jmpbuffer, 1);
}

    如果以带优化和不带优化选项编译后运行本程序,得到的结果是不一样的。
$ gcc longjmpDemo.c -o longjmpDemo.out    # 不进行任何优化的编译
$ ./longjmpDemo.out 
in f1():
exteval=94, global=95, autoval=96, regival=97, volaval=98, statval=99
after longjmp:
exteval=94, global=95, autoval=96, regival=97, volaval=98, statval=99
$ 
$ gcc -O longjmpDemo.c -o longjmpDemo2.out   # 进行全部优化的编译
$ ./longjmpDemo2.out 
in f1():
exteval=94, global=95, autoval=96, regival=97, volaval=98, statval=99
after longjmp:
exteval=94, global=95, autoval=2, regival=3, volaval=98, statval=99
$ 

    由此可见,全局变量、静态变量和易失变量不受优化的影响,在 longjmp 之后,它们的值是最近所呈现的值。setjmp 的手册页上说明,存放在存储器中的变量将具有 longjmp 时的值,而在 CPU 和浮点寄存器中的变量则恢复为调用 setjmp 时的值。由于当不进行优化时,这几个变量都存放在存储器中(即忽略了 register 存储类说明),而进行了优化后,autoval 和 regival 都存放在寄存器中(即使 autoval 没有用 register 说明),volatile 变量则仍存放在存储器中,所以才能看到上面的输出情况。因此若要编写一个使用非局部跳转的可移植程序,应该使用 volatile 属性。但是从一个系统移植到另一个系统,其他任何事情都可能改变。
    而关于自动变量,还有一种潜在的出错情况。基本规则是声明自动变量的函数返回后,不能再引用这些自动变量。下面这个函数就说明了自动变量的不正确使用的情况:它为它所打开的一个标准 I/O 流设置缓冲。
#include <stdio.h>

FILE *open_data(void){
	FILE *fp;
	char databuf[BUFSIZE];	// setvbuf makes this the stdio buffer.
	if((fp=fopen("datafile", "r")) == NULL)
		return NULL;
	if(setvbuf(fp, databuf, _IOLBF, BUFSIZE) != 0)
		return NULL;
	return fp;		// error
}

    这里存在的问题是,当 open_data 返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用。但是,标准 I/O 库函数仍将使用这部分存储空间作为该流的缓冲区,这就产生了冲突和混乱。所以正确的做法应是在全局存储空间静态地(如 static 或 extern)或者动态地为数组 databuf 分配空间。
  • 大小: 9.2 KB
  • 大小: 5.9 KB
分享到:
评论

相关推荐

    用 setjmp 和 longjmp 实现多线程(1)_多线程_

    在C语言中,setjmp和longjmp是两个与异常处理和非局部跳转相关的函数,它们可以被用来实现一种特殊的多线程效果。虽然这两个函数并非设计为创建和管理线程的标准方法,但在某些特定场景下,它们可以模拟多线程的行为...

    setjmp和longjmp详细介绍

    在C语言中,`setjmp`和`longjmp`提供了一种特殊的流程控制机制,允许程序执行从一个位置跳转到另一个位置,甚至跨越多个函数调用边界。这种机制类似于异常处理,但更加灵活和强大。本文将详细介绍这两个函数的工作...

    c标准异常处理-全面了解setjmp与longjmp的使用[参照].pdf

    在C语言中,setjmp和longjmp是一对用于异常处理和非局部跳转的函数,它们提供了在程序中实现类似于异常处理的机制。虽然C++有自己的异常处理框架,但setjmp和longjmp在某些特定场景下依然有其独特价值。 首先,...

    使用setjmp,longjmp函数模拟多任务实时操作系统

    3. 如果在之后的代码中调用`longjmp`,并传入之前保存的` jmp_buf`结构和一个非零值,程序会跳转回`setjmp`调用处,但这一次`setjmp`会返回`longjmp`传入的非零值,而不是0。 `longjmp`函数的使用: 1. `longjmp`有...

    深入解析:C语言中的`setjmp`和`longjmp`异常处理机制

    在C语言中,setjmp和longjmp函数提供了一种非局部跳转机制,允许程序从当前执行点跳转到之前保存的程序状态。这种机制通常用于错误处理和异常恢复,尤其是在需要从多层嵌套的函数调用中恢复时。本文将详细介绍setjmp...

    setjump与longjmp(异常处理机制)

    setjmp 和 longjmp 是 C 语言标准库中的两个函数,用于实现异常处理机制。它们提供了一种非本地局部跳转("non-local goto")机制,能够在程序中实现错误处理模块的调用和返回。 setjmp 函数的作用是保存程序当前的...

    浅析C语言中的setjmp与longjmp函数

    在C语言中,`setjmp` 和 `longjmp` 是两个非常特殊的函数,它们提供了非局部的跳转功能,能够实现在程序中的任意位置跳转到另一个位置,从而改变了正常的控制流程。这两个函数通常用于异常处理和错误恢复,因为它们...

    9. 进程的非局部跳转1

    非局部跳转则更为复杂,它允许程序跳转到当前函数外部的某个位置继续执行,这就需要用到`setjmp()`和`longjmp()`这两个函数。`setjmp()`在第一次调用时返回0,之后的调用则返回`longjmp()`调用时传入的值。它主要...

    C语言的setjmp:异常处理与构建协作式多任务系统

    本文将详细介绍`setjmp()`与`longjmp()`的工作原理及其应用场景,特别关注于异常处理和构建协作式多任务系统。 #### setjmp函数详解 `setjmp()`函数的主要作用是在特定位置保存当前的程序状态,以便稍后通过`...

    C++中的异常处理机制详解

    在C语言中,传统的错误处理方法有三种:在函数中返回错误、使用信号来做信号处理系统、使用标准C库中的非局部跳转函数setjmp和longjmp。这些方法都存在一些问题,如耦合度高、错误处理代码与正常代码混杂、对象不能...

    c++面试题网络编程篇

    本文总结了C++面试题网络编程篇中的重要知识点,包括dup和dup2函数、lseek函数、sync、fsync和fdatasync函数、fcntl函数、exit和_exit函数、setjmp和longjmp函数、记录锁、守护进程编程规范等。 1. dup和dup2函数 ...

    avr-libc-user-manual-1.6.1.rar_avr setjmp.lib_avr-li_avr-libc ma

    首先,AVR Setjmp库包含了setjmp和longjmp这两个关键函数,它们是实现非局部跳转的关键,常用于错误处理和多任务环境中的上下文切换。setjmp函数用于保存当前的运行环境,包括寄存器状态和堆栈指针,然后返回0。...

    嵌入式软件研发面试题目

    总结来说,这些面试题目主要考察了C语言中的`volatile`关键字的作用,非局部跳转`setjmp`和`longjmp`的使用,结构体指针的类型转换,以及递归函数的理解和应用。对于嵌入式软件研发的工程师来说,理解和掌握这些基础...

    C语言函数库详解.docx

    setjmp.h头文件提供了一组函数,用于实现非局部跳转。非局部跳转是指从一个函数跳转到另一个函数的过程。这些函数包括setjmp()和longjmp()。 signal.h:信号 signal.h头文件提供了一组函数,用于处理信号。信号是...

    glibc-2.29-5_C-C++_glibc2.29_glibc2.29源码_

    4. **setjmp**:这部分涉及到了非局部跳转(setjmp/longjmp)的功能,是C语言中用于异常处理和控制流转移的机制。 5. **resource**:这里包含了关于资源限制的函数,比如获取和设置内存限制、CPU时间限制等,对于...

    C函数速查手册

    7. **其他功能**:C语言还包括位操作、集合操作、时间处理等功能,手册会介绍相关函数如bitwise operators(位运算符)、setjmp/longjmp(非局部跳转)和time.h库中的时间函数。 8. **预处理器和宏**:C语言的预...

    c语言函数库c语言函数库

    9. 控制流函数:`exit`用于终止程序运行,`abort`强制程序异常退出,`setjmp`和`longjmp`用于非局部跳转。 10. 时间日期函数:`time`获取当前时间,`ctime`将时间戳转换为易读的日期和时间字符串,`difftime`计算两...

Global site tag (gtag.js) - Google Analytics