- 论坛徽章:
- 0
|
本帖最后由 KBTiller 于 2011-05-11 19:51 编辑
14.10 非本地跳转setjmp.h
“setjmp.h”中描述了一种数据类型、一个函数和一个类似函数的宏。这些都用于从一个函数“goto”到另一个函数。为了更好地解释其意义,下面借助一种假想的情况来说明。
假设从程序中的某点(S)开始,程序调用函数f1(),而函数f1()又调用f2(),f2()又调用f3()。
正常的运行路线是,执行完f3()返回f2(),从f2()再返回f1(),从f1()再返回S点继续执行。
但是可能有这样的情况,就是在执行f3()的过程中产生了某种“状况”,使得这种循规蹈矩的按照原路返回失去意义(况且这很费时),希望程序能够直接“抄近路”直接返回S点,恢复最初调用时的状态,重新再来进行调用。“setjmp.h”中描述的内容就是为了满足类似这样的“假如再回到从前”式的梦想和目的。
要实现这样的想法,首先需要制作一个路标,并且记录下当时的情境。“jmp_buf”这种数据类型的数据用于记录“当时的情境”。这种类型在本质上是一种数组,但这对于程序没有什么意义。我们只要知道这种类型的数据能够记录程序执行过程中的每一个现场情况就足够了,将来照样恢复现场的工作并不需要我们操心。
全方位地记录执行过程中某个瞬间的现场情况由类型函数的宏setjmp完成。
int setjmp(jmp_buf env);
其中的“env”就是对应于记录现场状况实参的形参。这是个类似函数的宏,姑且就当作函数吧,将会被调用两次。
第一次,在当地设置一个“路标”,以免将来返回的时候“迷路”,并且用“env”记录了现场的所有信息。完成了这件事情,这个函数的返回值为0。
当程序从某个其他函数需要返回到从前标记过的位置时,需要调用longjmp()函数。这个函数的原型是:
void longjmp(jmp_buf env, int val);
返回到当初被标记的位置时,setjmp()这个函数被第二次调用,你完全可以把这次调用看成是拆除“路标”并且用离开时记录的状态信息恢复当初的情境。这次setjmp()的返回值不是0而是调用longjmp()时与“val”对应的实参的哪个值。在调用longjmp()时,即使把对应“val”的实参写成0,setjmp()的返回值也不会是0。
这样,程序就实现了“假如再回到从前”式的愿望。但是也不能指望全部恢复。在setjmp()本地的auto类别的局部变量的值是不确定的,volatile类别的局部变量能保证是发生longjmp()时的值。其他可以保证具有确定值的对象的值也不是恢复到setjmp()时的值,而是发生longjmp()那一时刻的值。
下面的代码示意性地演示了这个过程。
程序代码14-7
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf hjzt ;// 用于记录环境状态
void f1 ( void );
void f2 ( void );
void f3 ( void );
int main( void )
{
if ( setjmp ( hjzt ) == 0 ) //这里设置路标
{
f1();
}
else
{
f1();
puts( "回到main" );
}
system("PAUSE");
return EXIT_SUCCESS;
}
void f1 ( void )
{
puts( "进入f1" );
f2();
puts( "回到f1" );
}
void f2 ( void )
{
puts( "进入f2" );
f3();
puts( "回到f2" );
}
void f3 ( void )
{
static int zc = 0 ; //用于设置如何返回
puts( "进入f3" );
if ( zc == 0 ) //这时直接跳回
{
zc = !zc ; //下次正常返回
puts( "离开f3\n" );
longjmp ( hjzt , 1 ) ; //那个1使下次setjmp(hjzt)的值为1
}
else
{//正常返回
puts( "离开f3" );
return ;
}
} |
|