只要经过 setjmp() 初始化,即可在任何时候调用 longjmp() 转跳到 setjmp() 函数位置,
setjmp() 与 longjmp()
原型:
void _Cdecl longjmp(jmp_buf jmpb, int retval);
int _Cdecl setjmp(jmp_buf jmpb);
在使用 setjmp() 与 longjmp() 之前,需要弄懂一个数据结构 jmp_buf,
此结构是 setjmp 接口(setjmp.h)的基本数据类型,jmp_buf 结构内容无须深入了解,
只需要知道,jmp_buf 将记录某一时刻CPU寄存器的值即可,setjmp() 函数可以返回两次,
第一次使用时作初始化一个 jmp_buf 的量(将当前CPU寄存器的值写入 jmpb 结构内),然后函数返回零;
第二次是由异常抛出函数 longjmp() 引发,使 setjmp() 函数产生第二次返回,返回值为 retval。
longjmp() 将 jmpb 内容恢复到寄存器内,执行位置将改变到 setjmp() 处,
在调用后,longjmp() 后面的代码将毫无意义,并且越过前面所有的函数(将不会再经过 return 逐层返回),
而是直接跳转到 setjmp() 位置。
_Cdecl 声明函数的参数使用标准的进栈方式(由右向左)压栈。
异常处理
goto 语句只可在函数内跳转,setjmp 与 longjmp 函数配合使用,
可以实现在函数之间跳转(远程跳转),
所以,C语言使用 setjmp() 及 longjmp() 实现异常处理机制。
setjmp() 设置跳转位置,longjmp()实现转跳。
在面向对象语言中由语言内部便提供了异常处理,如C++语言,
使用 try 块包裹可能产生异常的代码(包括函数调用),
catch 块用于编写异常处理代码;
异常由手动的 throw 语句抛出。
使用 C 简单地模拟 C++ 的异常处理语句是十分容易的,
此前,值得说明一点的是:jmp_buf 声明的变量多数情况下需要被声明为全局的,
这是因为 longjmp() 一般不在 try 块内部调用,try 块内一般会调用另外一些函数,
而 longjmp() 经常在这些函数内抛出。
jmp_buf Jump_Buffer;
#define try if(!setjmp(Jump_Buffer))
#define catch else
#define throw longjmp(Jump_Buffer, 1)
下面的例程解释如何使用这些宏:
/* 输入一个整型数,如果大于 100,则以异常抛出 */
#include "stdio.h"
#include "conio.h"
#include "setjmp.h"
jmp_buf Jump_Buffer;
#define try if(!setjmp(Jump_Buffer))
#define catch else
#define throw longjmp(Jump_Buffer, 1)
int Test(int T);
int Test_T(int T);
int Test(int T)
{
if(T > 100)
throw;
else
puts("OK.");
return;
}
int Test_T(int T)
{
Test(T);
return;
}
int main(void)
{
int T;
try
{
puts("Input a value:");
scanf("%d", &T);
T++;
Test_T(T);
}
catch
{
puts("Input Error!");
}
getch();
return 0;
}
当遇到 throw 抛出异常,立即转跳到 setjmp 处执行,
屏弃了与之无相关的枝节(函数的返回及 throw 其后的代码)
main [setjmp()] -> Test_T -> Test [throw]
↑ ↓
┗━━━━━━━━━━━━━━┛
当输入一个大于100的整数,throw 导致异常抛出,使用 1 返回到 setjmp() 函数处,
宏 try 使 if(!setjmp(Jump_Buffer)) 不成立,执行 catch 块,
catch 块是十分简单的 else 分支语句关键词的别称。
这一组宏完成了对 setjmp() 及 longjmp() 两个函数的封装,使程序具备简单的异常处理功能。
然而,遗憾的是这组宏不具备嵌套的能力,
当这组宏应用到嵌套异常,只能响应最后一组异常宏,
并且无法抛出异常类型,至少它连一个常量整型都无法抛出,
这是因为 jmp_buf 全局只能存放一个 jmpb 结构。
以下是对上面叙述的简单的图示:
main()
{
try
{
Test_T(); ━━━┑
} ┃
catch ┃
{ ┃
} ┃
} ┃
┃
┃
Test_T() <━━━━━━━━┛
{
try
{
Test(); ━━━┑
} ┃
catch <━━━━━━┿━━━━━┑
{ ┃ ┃
} ┃ ┃
} ┃ ┃
┃ ┃(只有最后一组宏可以响应异常)
┃ ┃
Test()<━━━━━━━━┛ ┃
{ ┃
if(True) ┃
throw; ━━━━━━━━━┛
}
虽然这组宏无法嵌套使用,然而抛出一个常量整型是有可能的(甚至是一个结构struct),
更改成如下一组宏,便可抛出一个常量整型,并且可以在 catch 处以 catch(Value) 的方式处理异常。
jmp_buf Jump_Buffer;
int TValue;
#define try if( !( TValue = setjmp(Jump_Buffer) ) )
#define catch(Val) else if(TValue == Val)
#define catch_all else
#define throw(Val) longjmp(Jump_Buffer, Val)
/* throw 抛出的值不应该等于0,因为这会导致无法执行try后面的catch块而继续执行形成了死循环*/
下面的例程演示了这组宏:
/* 输入一个整型数值,若大于 100 以异常抛出一个常量 20
否则以异常抛出一个常量 20 */
#include "stdio.h"
#include "conio.h"
#include "setjmp.h"
jmp_buf Jump_Buffer;
int TValue;
#define try if( !( TValue = setjmp(Jump_Buffer) ) )
#define catch(Val) else if(TValue == Val)
#define catch_all else
#define throw(Val) longjmp(Jump_Buffer, Val)
int Test(int T);
int Test_T(int T);
int Test(int T)
{
if(T > 100)
throw(20); /*只是演示,20这个常量值并无特别意义*/
throw(10); /* catch_all 块将处理这个异常*/
return;
}
int Test_T(int T)
{
Test(T);
return;
}
int main(void)
{
int T;
try
{
puts("Input a value:");
scanf("%d", &T);
T++;
Test_T(T);
}
catch(20)
{
puts("Input Error!(Code: 20)");
}
catch_all
{
puts("Unknown error!");
}
getch();
return 0;
}
正是因为 jmp_buf 全局只能存放一个 jmpb 结构,使得只有最后一组宏可以响应异常;
这是无法嵌套异常的原因,
要实现多重嵌套可以建立一个全局堆栈来维护一组 jmpb 结构,
此处不给出实现,若感兴趣请自行实现,文中若有错误,欢迎指正。
这里给出的只是C异常处理的简单实现,若要完善的异常处理,这需要更多的手段。
[此贴子已经被作者于2006-9-6 16:36:35编辑过]