`

全面了解setjmp与longjmp的使用

阅读更多

将对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语言中的异常处理机制时,会做详细 深入的研究,这里暂且跳过。

分享到:
评论

相关推荐

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

    因此,如果在setjmp和longjmp之间修改了这类变量,恢复后的状态可能与实际预期不符。 3. **栈的清理**:longjmp不会自动清理在它和setjmp之间创建的局部变量。这意味着,当控制流返回到setjmp保存的点时,这部分栈...

    c语言经典全面面试题

    了解如何使用setjmp和longjmp进行异常处理也很重要。 通过深入学习和练习这些C语言的经典面试题,不仅能提升编程技能,还能在面试中自信应对各种挑战,为职业发展打下坚实基础。《经典全面c面试题》这份资源无疑...

    C语言资料大全C语言资料大全

    - 异常处理:学习使用setjmp和longjmp实现非局部跳转,处理异常情况。 7. **标准库与算法** - 标准库函数:熟悉常用的数学、字符串、输入输出等标准库函数。 - 算法基础:介绍排序、搜索、图论等基础算法,并...

    你必须知道的495个C语言问题(完整版,含所有章节)

    - 异常处理:了解setjmp和longjmp函数进行非局部跳转,处理程序异常。 8. **标准库与标准输入输出** - 标准输入输出流:熟悉stdin、stdout和stderr,以及使用scanf和printf进行输入输出。 - 标准库函数:学习...

    c语言学习资料

    - 异常处理:了解错误处理机制,如errno和setjmp/longjmp。 3. **编程实践**: - 实战项目:通过编写简单的程序,如计算器、文本编辑器等,提升编程技能。 - 排序算法:学习冒泡排序、插入排序、选择排序、快速...

    你必须知道的495个C语言问题

    15. **异常处理**:虽然C语言没有内置的异常处理机制,但可以使用setjmp和longjmp实现类似的机制。 16. **性能优化**:通过理解C语言的底层特性,进行代码优化,例如减少不必要的内存分配,减少函数调用,合理使用...

    微软C编程精粹(doc格式).rar

    异常处理是现代编程中重要的错误处理机制,C语言提供了setjmp和longjmp函数进行非局部跳转,但通常在微软环境中,我们会更多地使用C++的try-catch机制,因为它提供了一种更优雅的错误处理方式。 最后,C编程还强调...

    985 211 高校计算机专业 考研复试 复习C语言 各学校真题和资料整理.zip

    在C语言的高级特性中,位操作、预处理宏、枚举、类型定义(typedef)和异常处理(setjmp/longjmp)等都需要理解。这些不仅可以提升编程效率,还能解决特定问题。 在复习过程中,历年真题的演练至关重要。通过解答...

    c知识类库c知识类库

    10. **异常处理**:虽然C语言没有内置的异常处理机制,但可以通过setjmp和longjmp实现类似的功能。 在描述中,虽然内容重复,但可以推测这是一个关于C语言学习资源的集合,可能包含了教程、示例代码或者相关的学习...

    C语言深度解剖完整版

    - 异常处理:了解C语言中的错误处理机制,如setjmp和longjmp。 7. **C标准库** - 标准库函数:介绍常用的库函数,如数学函数、字符串处理函数、输入输出函数等。 - 标准I/O流:探讨stdio.h中的文件流操作,如...

    你必须知道的495个C语言问题(完整版本)

    - 异常处理:了解何时使用setjmp和longjmp。 7. **高级主题**: - 位操作:理解位掩码和位字段。 - 内存对齐:了解编译器如何处理内存对齐,以及如何影响性能。 - 并发编程:涉及到线程、互斥锁、信号量等多...

    你必须知道的495个C语言问题-高清扫描版300dpi

    15. **异常处理**:虽然C语言本身不支持异常处理,但可以通过setjmp和longjmp模拟类似功能。 通过深入学习和实践这495个C语言问题,你将能全面地掌握C语言,并有能力解决实际开发中遇到的各种问题。无论是初学者...

    c语言实例精粹.rar

    实例可能涵盖动态内存分配(如malloc、calloc、realloc、free)、内存泄漏检测以及错误处理机制(如try-catch或setjmp/longjmp)的运用,这些都是提高程序稳定性和效率的关键。 总之,“c语言实例精粹.rar”是一个...

    C语言程序设计(谭浩强)_c程序_学习_

    25. 异常处理:了解C语言中的错误处理方式,如setjmp和longjmp。 以上知识点构成了《C语言程序设计》的核心内容,通过学习和实践这些概念,读者将能够熟练掌握C语言,为进一步学习其他编程语言和技术打下坚实基础。...

    C语言课件(讲的很详细)

    5. 异常处理:虽然C语言没有内置的异常处理机制,但可以借助setjmp和longjmp实现类似功能。 6. 标准库函数:深入学习标准库如stdio.h、stdlib.h、string.h等中的函数,提高编程效率。 实践应用: 1. 实现算法:...

    c语言实战典例

    - 异常处理:探讨C语言中的错误处理方式,如setjmp和longjmp函数。 - 进程与线程:简述C语言中实现进程和线程的基本方法,如fork、exec系列函数和pthread库。 7. **实战案例** - 105个实战例子涵盖了上述各个...

    LinuxC语言开发

    3.7 异常处理:setjmp/longjmp、signal处理异常情况。 3.8 系统调用接口:通过syscalls.h头文件直接调用内核功能,如open、read、write等。 四、嵌入式C语言开发实践 4.1 嵌入式系统概述:嵌入式系统的特性、分类及...

    c语言大全第4版+源码

    异常处理在C语言中并不直接支持,但可以通过setjmp和longjmp函数实现类似的功能,虽然不常用,但在特定情况下非常有用。 此外,C语言的标准库提供了大量的函数,如数学运算的math.h库,字符串处理的string.h库,...

    c语言编程宝典大全c语言编程宝典大全

    在C语言中,虽然没有像其他高级语言那样的异常系统,但我们需要掌握如何使用errno和setjmp/longjmp进行错误处理。 最后,理解C语言的内存管理,包括堆栈和堆的分配,以及内存对齐的概念,有助于写出更加高效且无...

    Linux程序设计权威指南

    8. **异常处理和错误检测**:学习如何使用setjmp/longjmp进行非局部跳转,以及理解和使用errno、perror等错误处理机制。 9. **安全性与权限**:理解用户和组的概念,以及访问控制列表(ACL)、文件权限和SELinux等...

Global site tag (gtag.js) - Google Analytics