setjmp与longjmp学习笔记

 

setjmplongjmp学习笔记

摘自

http://hi.baidu.com/yeqiwei/blog/item/abd187017297b4d6277fb58e.html

http://blog.codingnow.com/2010/05/setjmp.html

 

一、基础介绍

    头文件:#include<setjmp.h>

    原型:  int setjmp(jmp_buf envbuf)

    宏函数setjmp()在缓冲区envbuf中保存系统堆栈里的内容,供longjmp()以后使用,setjmp()必须使用头文件setjmp.h。首次调用setjmp()宏时,返回值为0,然而longjmp()把一个变原传递给setjmp(),该值(恒不为0)就是调用longjmp()后出现的setjmp()的值。

void longjmp(jmp_buf envbuf,int status);

    函数longjmp()使程序在最近一次调用setjmp()处重新执行。

    setjmp()longjmp()提供了一种在函数间调转的手段。函数longjmp()通过把堆栈复位成envbuf中描述的状态进行操作,envbuf的设置是由预先调用setjmp()生成的。这样使程序的执行在setjmp()调用后的下一个语句从新开始,使计算机认为从未离开调用setjmp()的函数。从效果上看,longjmp()函数似乎过了时间和空间(内存)回到程序的原点,不必执行正常的函数返回过程。

    缓冲区envbuf具有<setjmp.h>中定义的buf_jmp类型,它必须调用longjmp()前通过调用setjmp()来设置好,而值status变成setjmp()的返回值,由此确定长调转的来处。不允许的唯一值是00是程序直接调用函数setjmp()时由该函数返回的,不是间接通过执行函数longjmp()返回的。longjmp()函数最常用于在一个错误发生时,从一组深层嵌套的实用程序中返回。

    简单实例:

 

程序输出: 1 2 3

 

    在linux内核中,异常大多使用goto来处理。实际上goto语句是面向过程与面向结构化程序语言中,进行异常处理编程的最原始的支持形式。后来为了更好地、更方便地支持异常处理编程机制,使得程序员在C语言开发的程序中,能写出更高效、更友善的带有异常处理机制的代码模块来。于是,C语言中出现了一种更优雅的异常处理机制,那就是setjmp()函数与longjmp()函数。

    实际上,这种异常处理的机制不是C语言中自身的一部分,而是在C标准库中实现的两个非常有技巧的库函数,也许大多数C程序员朋友们对它都很熟悉,而且,通过使用setjmp()函数与longjmp()函数组合后,而提供的对程序的异常处理机制,以被广泛运用到许多C语言开发的库系统中,如jpg解析库,加密解密库等等。

    也许C语言中的这种异常处理机制,较goto语句相比较,它才是真正意义上的、概念上比较彻底的,一种异常处理机制。作风一向比较严谨、喜欢刨根问底的主人公阿愚当然不会放弃对这种异常处理机制进行全面而深入的研究。下面一起来看看。

 

二、setjmp的使用分析

    前面刚说了,setjmpC标准库中提供的一个函数,它的作用是保存程序当前运行的一些状态。

    它的函数原型如下:int setjmp( jmp_buf env );

  这是MSDN中对它的评论,如下:

   setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。当setjmplongjmp组合一起使用时,它们能提供一种在程序中实现非本地局部跳转"non-local goto")的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块之中;或者程序中不采用正常的返回(return)语句,或函数的正常调用等方法,而使程序能被恢复到先前的一个调用例程(也即函数)中。

  对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且当前的程序控制流,会因此而返回到先前调用setjmp时的程序执行点。此时,在接下来的控制流的例程中,所能访问的所有的变量(除寄存器类型的变量以外),包含了longjmp函数调用时,所拥有的变量。

  setjmplongjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,请使用C++提供的异常处理机制。

 

三、longjmp的使用分析

    同样,longjmp也是C标准库中提供的一个函数,它的作用是用于恢复程序执行的堆栈环境,

    它的函数原型如下: void longjmp( jmp_buf env, int value );

  这是MSDN中对它的评论,如下:

  longjmp函数用于恢复先前程序中调用的setjmp函数时所保存的堆栈环境。setjmplongjmp组合一起使用时,它们能提供一种在程序中实现非本地局部跳转"non-local goto")的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块,或者不采用正常的返回(return)语句,或函数的正常调用等方法,使程序能被恢复到先前的一个调用例程(也即函数)中。

  对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且因此当前的程序控制流,会返回到先前调用setjmp时的执行点。此时,value参数值会被setjmp函数所返回,程序继续得以执行。并且,在接下来的控制流的例程中,它所能够访问到的所有的变量(除寄存器类型的变量以外),包含了longjmp函数调用时,所拥有的变量;而寄存器类型的变量将不可预料。setjmp函数返回的值必须是非零值,如果longjmp传送的value参数值为0,那么实际上被setjmp返回的值是1

  在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料。

在使用longjmp时,请遵守以下规则或限制:
   ① 不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过setjmp所返回的控制流中,例程中寄存器类型的变量将不会被恢复。

      ② 不要使用longjmp函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用_fpreset函数,来首先初始化浮点数包后,它是可以通过longjmp来实现从中断处理例程中返回。

      ③ 在C++程序中,小心对setjmplongjmp的使用,应为setjmplongjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,使用C++提供的异常处理机制将会更加安全。

      把setjmplongjmp组合起来,原来它这么厉害!

 

 

四、深入分析 

      setjmp C 语言解决 exception 的标准方案。

      setjmp/longjmp 这组 api 的名字没有取好,导致了许多误解。名字体现的是其行为:跳转,却没能反映其功能:exception 的抛出和捕获。

      longjmp 从名字上看,叫做长距离跳转。实际上它能做的事情比名字上看起来的要少得多。跳转并非从静止状态的代码段的某个点跳转到另一个位置(类似在汇编层次的 jmp 指令做的那样),而是在运行态中向前跳转。C 语言的运行控制模型,是一个基于栈结构的指令执行序列。表示出来就是 call / return :调用一个函数,然后用 return 指令从一个函数返回。setjmp/longjmp 实际上是完成的另一种调用返回的模型。setjmp 相当于 call longjmp 则是 return

      重要的区别在于:setjmp 不具备函数调用那样灵活的入口点定义;而 return 不具备 longjmp 那样可以灵活的选择返回点。其次,第一、setjmp 并不负责维护调用栈的数据结构,即,你不必保证运行过程中 setjmp longjmp 层次上配对。如果需要这种层次,则需要程序员自己维护一个调用栈。这个调用栈往往是一个 jmp_buf 的序列;第二、它也不提供调用参数传递的功能,如果你需要,也得自己来实现。

      以库形式提供的 setjmp/longjmp 和以语言关键字 return 提供的两套平行的运行流控制放在一起,大大拓展了 C 语言的能力。把 setjmp/longjmp 嵌在单个函数中使用,可以模拟 pascal 中嵌套函数定义:即在函数中定义一个局部函数ps. GNUC 扩展了 C

言,也在语法上支持这种定义方法。这种用法可以让几个局部函数有访问和共享 upvalue 的能力。

      把 setjmp/longjmp 放在大框架上,则多用来模拟 exception 机制。setjmp 也可以用来模拟 coroutine 。但是会遇到一个难以逾越的难点:正确的 coroutine 实现需要为每个 coroutine 配备一个独立的数据栈,这是 setjmp 无法做到的。虽然有一些 C coroutine 库用 setjmp/longjmp 实现。但使用起来都会有一定隐患。多半是在单一栈上预留一块空间,然后给另一个 coroutine 运行时覆盖使用。当数据栈溢出时,程序会发生许多怪异的现象,很难排除这种溢出 bug 。要正确的实现 coroutine ,还需要 setcontext 库 ,这已经不是 C 语言的标准库了。

      在使用 setjmp 时,最常见的一个错误用法就是对 setjmp 做封装,用一个函数去调用它。比如:

int try(breakpoint bp)

{

      return setjmp(bp->jb);

}

void throw(breakpoint bp)

{

      longjmp(bp->jb,1);

}

      setjmp 不应该封装在一个函数中。这样写并不讳引起编译错误。但十有八九会引起运行期错误。错误的起源在于 longjmp 的跳转返回点,必须在运行流经过并有效的位置。而如果对 setjmp 做过一层函数调用的封装后。上例中的 setjmp 设置的返回点经过 try 的调用返回后,已经无效。如果要必要封装的话,应该使用宏。

      setjmp/longjmp 对于大多数 C 程序员来说比较陌生。正是在于它的定义含糊不清,不太容易弄清楚。使用上容易出问题,运用场合也就变的很狭窄,多用于规模较大的库或框架中。和 C++ 语言提供的 execption 机制一样,很少有构架师愿意把它暴露到外面,那需要对二次开发的程序员有足够清晰的头脑,并充分理解其概念才不会用错。这往往是不可能的。另外,setjmp/longjmp 的理念和 C++ 本身的 RAII 相冲突。虽然许多编译器为防止 C++ 程序员错误使用 setjmp 都对其做了一定的改进。让它可以正确工作。但大多数情况下,还是在文档中直接声明不推荐在 C++ 程序中使用这个东西。

 

一个MSDN的实例

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值