- 浏览: 140770 次
文章分类
最新评论
C 语言中的 goto 语句是不能跨越函数的,属于局部性跳转,若要执行跨函数跳转,可使用 setjmp 和 longjmp 函数,它们对于处理发生在很深层嵌套函数调用中的出错情况时非常有用。
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 后各种类型的变量的变化情况。
如果以带优化和不带优化选项编译后运行本程序,得到的结果是不一样的。
由此可见,全局变量、静态变量和易失变量不受优化的影响,在 longjmp 之后,它们的值是最近所呈现的值。setjmp 的手册页上说明,存放在存储器中的变量将具有 longjmp 时的值,而在 CPU 和浮点寄存器中的变量则恢复为调用 setjmp 时的值。由于当不进行优化时,这几个变量都存放在存储器中(即忽略了 register 存储类说明),而进行了优化后,autoval 和 regival 都存放在寄存器中(即使 autoval 没有用 register 说明),volatile 变量则仍存放在存储器中,所以才能看到上面的输出情况。因此若要编写一个使用非局部跳转的可移植程序,应该使用 volatile 属性。但是从一个系统移植到另一个系统,其他任何事情都可能改变。
而关于自动变量,还有一种潜在的出错情况。基本规则是声明自动变量的函数返回后,不能再引用这些自动变量。下面这个函数就说明了自动变量的不正确使用的情况:它为它所打开的一个标准 I/O 流设置缓冲。
这里存在的问题是,当 open_data 返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用。但是,标准 I/O 库函数仍将使用这部分存储空间作为该流的缓冲区,这就产生了冲突和混乱。所以正确的做法应是在全局存储空间静态地(如 static 或 extern)或者动态地为数组 databuf 分配空间。
#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 分配空间。
发表评论
-
打开伪终端设备
2018-07-09 20:50 1248在伪终端概述一节中已对 PTY进行了初步的介绍。尽管 ... -
伪终端概述
2018-06-02 11:05 1533伪终端就是指,一个应用程序看上去像一个终端,但事实上它 ... -
终端窗口大小和 termcap
2018-05-29 22:39 791多数 UNIX 系统都提供了一种跟踪当前终端窗口大小的 ... -
终端规范模式和非规范模式
2018-05-29 00:25 937终端规范模式很简单:发一个读请求,当一行已经输入后,终 ... -
终端标识
2018-05-23 11:18 566尽管控制终端的名字在多数 UNIX 系统上都是 /de ... -
波特率和行控制函数
2018-05-22 07:53 933虽然大多数终端设 ... -
终端属性和选项标志
2018-05-20 07:40 706tcgetattr 和 tcsetattr ... -
终端特殊输入字符
2018-05-17 06:33 807终端支持下表所示的特殊输入字符。 为了更改 ... -
终端 I/O 综述
2018-05-10 07:56 432终端设备可认为是由内核中的终端驱动程序控制的。每个终端 ... -
POSIX 信号量
2018-05-09 00:03 575在XSI IPC通信之信 ... -
XSI IPC 通信之共享存储
2018-04-25 07:18 943在XSI IPC通信之消息队列和XSI IPC通信之信 ... -
XSI IPC通信之信号量
2018-04-17 23:38 612在XSI IPC通信之消 ... -
XSI IPC通信之消息队列
2018-04-15 10:54 490消息队列是消息的链接表,存储在内核中,由消息队列标识符 ... -
XSI IPC 相似特征介绍
2018-02-08 23:48 479有 3 种称作 XSI IPC ... -
IPC 通信之 FIFO
2018-02-06 22:55 412FIFO 也被称为命名管道,未命名的管道只能在两个相关 ... -
IPC 通信之管道
2018-01-30 22:22 383管道是 UNIX 系统 IPC 的最古老但也是最常用的 ... -
readv/writev 函数及存储映射 I/O
2018-01-19 00:57 878readv 和 writev 函数可用于在一次函数调用 ... -
POSIX 异步 I/O
2018-01-16 21:33 449POSIX 异步 I/O 接口为对不同类型的文件进行异 ... -
fcntl 记录锁
2018-01-06 23:48 584记录锁的功能是:当有进程正在读或修改文件的某个部分时, ... -
守护进程惯例
2018-01-06 23:52 432UNIX 系统中,守护进程遵循下列通用惯例。 ...
相关推荐
在C语言中,setjmp和longjmp是两个与异常处理和非局部跳转相关的函数,它们可以被用来实现一种特殊的多线程效果。虽然这两个函数并非设计为创建和管理线程的标准方法,但在某些特定场景下,它们可以模拟多线程的行为...
在C语言中,`setjmp`和`longjmp`提供了一种特殊的流程控制机制,允许程序执行从一个位置跳转到另一个位置,甚至跨越多个函数调用边界。这种机制类似于异常处理,但更加灵活和强大。本文将详细介绍这两个函数的工作...
在C语言中,setjmp和longjmp是一对用于异常处理和非局部跳转的函数,它们提供了在程序中实现类似于异常处理的机制。虽然C++有自己的异常处理框架,但setjmp和longjmp在某些特定场景下依然有其独特价值。 首先,...
3. 如果在之后的代码中调用`longjmp`,并传入之前保存的` jmp_buf`结构和一个非零值,程序会跳转回`setjmp`调用处,但这一次`setjmp`会返回`longjmp`传入的非零值,而不是0。 `longjmp`函数的使用: 1. `longjmp`有...
在C语言中,setjmp和longjmp函数提供了一种非局部跳转机制,允许程序从当前执行点跳转到之前保存的程序状态。这种机制通常用于错误处理和异常恢复,尤其是在需要从多层嵌套的函数调用中恢复时。本文将详细介绍setjmp...
setjmp 和 longjmp 是 C 语言标准库中的两个函数,用于实现异常处理机制。它们提供了一种非本地局部跳转("non-local goto")机制,能够在程序中实现错误处理模块的调用和返回。 setjmp 函数的作用是保存程序当前的...
在C语言中,`setjmp` 和 `longjmp` 是两个非常特殊的函数,它们提供了非局部的跳转功能,能够实现在程序中的任意位置跳转到另一个位置,从而改变了正常的控制流程。这两个函数通常用于异常处理和错误恢复,因为它们...
非局部跳转则更为复杂,它允许程序跳转到当前函数外部的某个位置继续执行,这就需要用到`setjmp()`和`longjmp()`这两个函数。`setjmp()`在第一次调用时返回0,之后的调用则返回`longjmp()`调用时传入的值。它主要...
本文将详细介绍`setjmp()`与`longjmp()`的工作原理及其应用场景,特别关注于异常处理和构建协作式多任务系统。 #### setjmp函数详解 `setjmp()`函数的主要作用是在特定位置保存当前的程序状态,以便稍后通过`...
在C语言中,传统的错误处理方法有三种:在函数中返回错误、使用信号来做信号处理系统、使用标准C库中的非局部跳转函数setjmp和longjmp。这些方法都存在一些问题,如耦合度高、错误处理代码与正常代码混杂、对象不能...
本文总结了C++面试题网络编程篇中的重要知识点,包括dup和dup2函数、lseek函数、sync、fsync和fdatasync函数、fcntl函数、exit和_exit函数、setjmp和longjmp函数、记录锁、守护进程编程规范等。 1. dup和dup2函数 ...
首先,AVR Setjmp库包含了setjmp和longjmp这两个关键函数,它们是实现非局部跳转的关键,常用于错误处理和多任务环境中的上下文切换。setjmp函数用于保存当前的运行环境,包括寄存器状态和堆栈指针,然后返回0。...
总结来说,这些面试题目主要考察了C语言中的`volatile`关键字的作用,非局部跳转`setjmp`和`longjmp`的使用,结构体指针的类型转换,以及递归函数的理解和应用。对于嵌入式软件研发的工程师来说,理解和掌握这些基础...
setjmp用于设置一个跳转点,而longjmp用于从当前函数跳出,跳转到由setjmp设置的跳转点,这可以用来实现非局部的跳转。然而,setjmp和longjmp并不直接抛出或捕获异常,它们可以用于替代异常处理结构,但在C++中,...
setjmp.h头文件提供了一组函数,用于实现非局部跳转。非局部跳转是指从一个函数跳转到另一个函数的过程。这些函数包括setjmp()和longjmp()。 signal.h:信号 signal.h头文件提供了一组函数,用于处理信号。信号是...
4. **setjmp**:这部分涉及到了非局部跳转(setjmp/longjmp)的功能,是C语言中用于异常处理和控制流转移的机制。 5. **resource**:这里包含了关于资源限制的函数,比如获取和设置内存限制、CPU时间限制等,对于...
7. **其他功能**:C语言还包括位操作、集合操作、时间处理等功能,手册会介绍相关函数如bitwise operators(位运算符)、setjmp/longjmp(非局部跳转)和time.h库中的时间函数。 8. **预处理器和宏**:C语言的预...
`setjmp()`和`longjmp()`这两个函数组合使用,可以实现非局部的控制流转移,常用于异常处理和多返回路径的复杂情况。 8. `<signal.h>`:信号 信号是进程间通信的一种方式,`<signal.h>`定义了处理信号的函数,如`...
setjmp和longjmp是C语言中用于非局部跳转的函数,setjmp用于保存当前的调用环境,并返回给调用者一个非零值;而longjmp用于恢复之前保存的调用环境。这两个函数通常用于错误处理,以实现从深层嵌套函数调用中跳转...