将对setjmp与longjmp的具体使用方法和适用的场合,进行一个非常全面的阐述。

另外请特别注意,setjmp函数与longjmp函数总是组合起来使用,它们是紧密相关的一对操作,只有将它们结合起来使用,才能达到程序控制流有效转移的目的,才能按照程序员的预先设计的意图,去实现对程序中可能出现的异常进行集中处理。

  与goto语句的作用类似,它能实现本地的跳转

  这种情况容易理解,不过还是列举出一个示例程序吧!如下:

void main( void )
{
int jmpret;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(1) longjmp(mark, 1);

// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(2) longjmp(mark, 2);

// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(-1) longjmp(mark, -1);

// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}

return;
}

  上面的例程非常地简单,其中程序中使用到了异常处理的机制,这使得程序的代码非常紧凑、清晰,易于理解。在程序运行过程中,当异常情况出现后,控制流是进行了一个本地跳转(进入到异常处理的代码模块,是在同一个函数的内部),这种情况其实也可以用goto语句来予以很好的实现,但是,显然setjmp与 longjmp的方式,更为严谨一些,也更为友善。程序的执行流如图17-1所示。

setjmp与longjmp相结合,实现程序的非本地的跳转

  呵呵!这就是goto语句所不能实现的。也正因为如此,所以才说在C语言中,setjmp与longjmp相结合的方式,它提供了真正意义上的异常处理机制。其实上一篇文章中的那个例程,已经演示了longjmp函数的非本地跳转的场景。这里为了更清晰演示本地跳转与非本地跳转,这两者之间的区别,我们在上面刚才的那个例程基础上,进行很小的一点改动,代码如下:

void Func1()
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(1) longjmp(mark, 1);
}

void Func2()
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(2) longjmp(mark, 2);
}

void Func3()
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(-1) longjmp(mark, -1);
}

void main( void )
{
int jmpret;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行

// 下面的这些函数执行过程中,有可能出现异常
Func1();

Func2();

Func3();

// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}

return;
}

  回顾一下,这与C++中提供的异常处理模型是不是很相近。异常的传递是可以跨越一个或多个函数。这的确为C程序员提供了一种较完善的异常处理编程的机制或手段。

setjmp和longjmp使用时,需要特别注意的事情

  1、setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,也即先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。否则,如果在setjmp调用之前,执行longjmp函数,将导致程序的执行流变的不可预测,很容易导致程序崩溃而退出。请看示例程序,代码如下:

class Test
{
public:
Test()
~Test()
}obj;

//注意,上面声明了一个全局变量obj

void main( void )
{
int jmpret;

// 注意,这里将会导致程序崩溃,无条件退出
Func1();
while(1);

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行

// 下面的这些函数执行过程中,有可能出现异常
Func1();

Func2();

Func3();

// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}

return;
}

  上面的程序运行结果,如下:
  构造对象
  Press any key to continue

  的确,上面程序崩溃了,由于在Func1()函数内,调用了longjmp,但此时程序还没有调用setjmp来保存一个程序执行点。因此,程序的执行流变的不可预测。这样导致的程序后果是非常严重的,例如说,上面的程序中,有一个对象被构造了,但程序崩溃退出时,它的析构函数并没有被系统来调用,得以清除一些必要的资源。所以这样的程序是非常危险的。(另外请注意,上面的程序是一个C++程序,所以大家演示并测试这个例程时,把源文件的扩展名改为 xxx.cpp)。

  2、除了要求先调用setjmp函数,之后再调用longjmp函数(也即longjmp必须有对应的setjmp函数)之外。另外,还有一个很重要的规则,那就是longjmp的调用是有一定域范围要求的。这未免太抽象了,还是先看一个示例,如下:

int Sub_Func()
{
int jmpret, be_modify;

be_modify = 0;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}

//注意这一语句,程序有条件地退出
if (be_modify==0) exit(0);
}

return jmpret;
}

void main( void )
{
Sub_Func();

// 注意,虽然longjmp的调用是在setjmp之后,但是它超出了setjmp的作用范围。
longjmp(mark, 1);
}

  如果你运行或调试(单步跟踪)一下上面程序,发现它真是挺神奇的,居然longjmp执行时,程序还能够返回到setjmp的执行点,程序正常退出。但是这就说明了上面的这个例程的没有问题吗?我们对这个程序小改一下,如下:

int Sub_Func()
{
// 注意,这里改动了一点
int be_modify, jmpret;

be_modify = 0;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}

//注意这一语句,程序有条件地退出
if (be_modify==0) exit(0);
}

return jmpret;
}

void main( void )
{
Sub_Func();

// 注意,虽然longjmp的调用是在setjmp之后,但是它超出了setjmp的作用范围。
longjmp(mark, 1);
}

  运行或调试(单步跟踪)上面的程序,发现它崩溃了,为什么?这就是因为,“在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料” (这在上一篇文章中已经提到过,MSDN中做了特别的说明)。为什么这样做会导致不可预料?其实仔细想想,原因也很简单,那就是因为,当setjmp函数调用时,它保存的程序执行点环境,只应该在当前的函数作用域以内(或以后)才会有效。如果函数返回到了上层(或更上层)的函数环境中,那么setjmp保存的程序的环境也将会无效,因为堆栈中的数据此时将可能发生覆盖,所以当然会导致不可预料的执行后果。

   3、不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过setjmp所返回的控制流中,例程中寄存器类型的变量将不会被恢复。(MSDN中做了特别的说明,上一篇文章中,这也已经提到过)。寄存器类型的变量,是指为了提高程序的运行效率,变量不被保存在内存中,而是直接被保存在寄存器中。寄存器类型的变量一般都是临时变量,在C语言中,通过register定义,或直接嵌入汇编代码的程序。这种类型的变量一般很少采用,所以在使用setjmp和longjmp时,基本上不用考虑到这一点。

  4、MSDN中还做了特别的说明,“在C+ +程序中,小心对setjmp和longjmp的使用,因为setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,使用C++提供的异常处理机制将会更加安全。”虽然说C++能非常好的兼容C,但是这并非是100%的完全兼容。例如,这里就是一个很好的例子,在C++ 程序中,它不能很好地与setjmp和longjmp和平共处。在后面的一些文章中,有关专门讨论C++如何兼容支持C语言中的异常处理机制时,会做详细深入的研究,这里暂且跳过。

[转]全面了解setjmp与longjmp的使用相关推荐

  1. Linux学习之setjmp和longjmp函数

    nsetjmp和longjmp函数实现函数之间的跳转(需包含头文件"setjmp.h"): 函数原型:int setjmp(jmp_buf env);   void longjmp ...

  2. 递归和函数栈与setjmp和longjmp的关系

    递归每执行一次都会释放一次函数栈 setjmp 记录函数栈的栈顶 longjmp 寻找函数栈的栈顶 如果longjmp找到了他要寻找的函数栈顶 调用setjmp的函数栈不会被释放 所以setjmp 和 ...

  3. setjmp()、longjmp() Linux Exception Handling/Error Handling、no-local goto

    目录 1. 应用场景 2. Use Case Code Analysis 3. 和setjmp.longjmp有关的glibc and eglibc 2.5, 2.7, 2.13 - Buffer O ...

  4. C异常处理实现: setjmp和longjmp

    此文为internet上选摘,过后我会用自己的理解补充此文. ------------ 将对setjmp与longjmp的具体使用方法和适用的场合,进行一个非常全面的阐述. 另外请特别注意,setjm ...

  5. 【转】浅析C语言的非局部跳转:setjmp和longjmp

    转自 http://www.cnblogs.com/lienhua34/archive/2012/04/22/2464859.html C语言中有一个goto语句,其可以结合标号实现函数内部的任意跳转 ...

  6. 不同函数之间的跳转setjmp和longjmp

    我们学过C语言中的goto,只能当前函数里面跳转是不能跨越函数的.setjmp和longjmp却可以跨越函数跳转. #include <stdio.h> #include <setj ...

  7. C异常处理机制:setjmp和longjmp

    setjmp()和longjum()是通过操纵过程活动记录实现的.它是C语言所独有的.它们部分你不了C语言有限的转移能力.这个两个函数协同工作,如下所示:     *setjmp(jmp_buf j) ...

  8. 利用C语言中的setjmp和longjmp,来实现异常捕获和协程

    一.前言 二.函数语法介绍 与 goto 语句比较 与 fork 函数比较 与 Python 语言中的 yield/resume 比较 三.利用 setjmp/longjmp 实现异常捕获 四.利用 ...

  9. linux C下的setjmp与longjmp介绍

    setjmp和longjmp是C语言独有的,只有将它们结合起来使用,才能达到程序控制流有效转移的目的,按照程序员的预先设计的意图,去实现对程序中可能出现的异常进行集中处理. 先来看一下这两个函数的定义 ...

最新文章

  1. 复习计算机网络基础 day8--传输层
  2. UVA11992不错的线段树段更新
  3. HDU4099(斐波那契数列与字典树)
  4. 快速理解ASP.NET Core的认证与授权
  5. 前端学习(3074):vue+element今日头条管理-删除文章处理完成
  6. vector 不初始化时什么状态_Vue原理解析(三):初始化时created之前做了什么?...
  7. Qt文档阅读笔记-QSslSocket中与错误相关的信号
  8. calender get方法_Calendar.get()方法--- WEEK_OF_YEAR 、MONTH、
  9. 在React.js中执行反跳
  10. ASIHttpRequest startAsynchronous
  11. vim 编辑二进制文件
  12. C#测绘兰勃特墨卡托投影
  13. 多线程——start()和run()
  14. C++校内模拟赛-06水题
  15. 豆瓣电影数据可视化大屏
  16. google vr 入门之VrPanoramaView制作全景图列表,apm应用性能管理
  17. 串行通信协议(I2C、SPI、UART、SCCB、CAN)
  18. 计算机组装维修考点,计算机维修工(基础知识)—教材
  19. 计算机睡眠状态游戏还在运行,win10睡眠主机还在运行正常嘛_win10睡眠模式主机还运行如何处理-win7之家...
  20. AMD的复兴之路 发力ARM服务器芯片

热门文章

  1. 51`CTO下载中心——我的新爱
  2. Server.MapPath(path)的使用
  3. flex制作swc文件
  4. hdu 5172(RMQ+前缀和)
  5. NYOJ 275 队花的烦恼一
  6. Golang 为什么不能直接将任意类型数组赋值给 []interface{}完成泛型操作
  7. 二进制包方式安装部署三节点k8s
  8. 命名空间和模块化编程3
  9. 七.Hystrix Timeout机制
  10. ibatis mybatis传入List参数