澳门新葡亰网址下载C语言中异常处理的两个函数

by admin on 2020年2月2日

与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。
为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件<setjmp.h>申明了这些函数及同时所需的jmp_buf数据类型。
setjmp.h是C标准函数库中提供“非本地跳转”的头文件:控制流偏离了通常的子程序调用与返回串行。互补的两个函数setjmp与longjmp提供了这种功能。

C语言异常处理机制——为您的C程序添加异常处理

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

1、什么是异常

setjmp保存当前的环境(即程序的状态)到平台相关的一个数据结构
(jmp_buf),该数据结构在随后程序执行的某一点可被
longjmp用于恢复程序的状态到setjmp调用所保存到jmp_buf时的原样。这一过程可以认为是”跳转”回setjmp所保存的程序执行状态。setjmp的返回值指出控制是正常到达该点还是通过调用longjmp恢复到该点。

        异常一般指的是程序运行期(Run-Time)发生的非正常情况。
        异常一般是不可预测的,如:内存不足、打开文件失败、范围溢出等。
        UNIX
使用信号给出异常,并当发生异常时转跳到信号处理过程进行异常处理。DOS下的信号对比UNIX系统而言相对较少。
        C标准库提供两个特殊的函数:setjmp() 及
longjmp(),这两个函数是结构化异常的基础,正是利用这两个函数的特性来实现异常。
        所以,异常的处理过程可以描述为这样:
        首先设置一个跳转点(setjmp()
函数可以实现这一功能),然后在其后的代码中任意地方调用 longjmp()
跳转回这个跳转点上,以此来实现当发生异常时,转到处理异常的程序上,在其后的介绍中将介绍如何实现。
        setjmp() 为跳转返回保存现场并为异常提供处理程序,longjmp()
则进行跳转(抛出异常),setjmp() 与 longjmp()
可以在函数间进行跳转,这就像一个全局的 goto 语句,可以跨函数跳转。
        举个例子,程序在 main() 函数内使用 setjmp()
设置跳转,并调用另一函数A,函数A内调用B,B抛出异常(调用longjmp()
函数),则程序直接跳回到 main() 函数内使用 setjmp()
的地方返回,并且返回一个值。

原理非常简单:
1.setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。
2.
以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0。)
jmp_buf 数组类型,例如struct int[16]或struct
__jmp_buf_tag,用于保存恢复调用环境所需的信息

2、jmp_buf 异常结构

通过有两类返回值,setjmp()让你知道它正在被怎么使用。当设置j时,setjmp()如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常,用setjmp()标记相应的异常处理程序。
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void raise_exception(void)
{
printf(“exception raisedn”);
longjmp(j, 1); /* jump to exception handler */
printf(“this line should never appearn”);
}
澳门新葡亰网址下载 ,int main(void)
{
if(setjmp(j) == 0)
{
printf(“”setjmp” is initializing ”j”n”);
raise_exception();
printf(“this line should never appearn”);
}
else
{
printf(“”setjmp” was just jumped inton”);
/* this code is the exception handler */
}
return 0;
}
/* When run yields:
”setjmp” is initializing ”j”
exception raised
”setjmp” was just jumped into
*/
那个填充jmp_buf的函数不在调用longjmp()之前返回。否则,存储在jmp_buf中的上下文就有问题了:
jmp_buf j;
void f(void)
{
setjmp(j);
}
int main(void)
{
f();
longjmp(j, 1); /* logic error */
return 0;
}
所以,你必须把setjmp()处理成只是到其所在位置的一个非局部跳转。
Longjmp()和setjmp()联合体运行于异常生命期的2和3阶段。longjmp(j,r)产生异常对象r(一个整数),并且作为返回值传送到setjmp(j)处。实际上,setjmp()函数通报了异常r。
下面这个例子采用switch,能更好的展现这对函数的功能:
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void raise_exception(void)
{
printf(“exception raisedn”);
longjmp(j, 3); /* jump to exception handler case 3 */
printf(“this line should never appearn”);
}
int main(void)
{
switch (setjmp(j))
{
case 0:
printf(“”setjmp” is initializing ”j”n”);
raise_exception();
printf(“this line should never appearn”);
case 1:
printf(“Case 1n”);break;
case 2:
printf(“Case 2n”);break;
case 3:
printf(“Case 3n”);break;
default:
break;
}
return 0;
}

        使用 setjmp() 及 longjmp() 函数前,需要先认识一下 jmp_buf
异常结构。jmp_buf 将使用在 setjmp()
函数中,用于保存当前程序现场(保存当前需要用到的寄存器的值),jmp_buf
结构在 setjmp.h 文件内声明:

        typedef struct
        {
                unsigned j_sp;  // 堆栈指针寄存器
                unsigned j_ss;  // 堆栈段
                unsigned j_flag;  // 标志寄存器
                unsigned j_cs;  // 代码段
                unsigned j_ip;  // 指令指针寄存器
                unsigned j_bp; // 基址指针
                unsigned j_di;  // 目的指针
                unsigned j_es; // 附加段
                unsigned j_si;  // 源变址
                unsigned j_ds; // 数据段
        } jmp_buf;

        jmp_buf 结构存放了程序当前寄存器的值,以确保使用 longjmp()
后可以跳回到该执行点上继续执行。

3、setjmp() 与 longjmp() 函数详细说明

        setjmp() 与 longjmp() 函数原型如下:
                void _Cdecl longjmp(jmp_buf jmpb, int retval);
                int _Cdecl setjmp(jmp_buf jmpb);

        _Cdecl
声明函数的参数使用标准C的进栈方式(由右向左)压栈,_Cdecl
是C语言的一种调用约定,除此以

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图