当前位置: 代码迷 >> C语言 >> 请教signal的问题
  详细解决方案

请教signal的问题

热度:364   发布时间:2006-09-06 13:09:22.0
老兄,我还希望在信号处理函数里面,通过longjmp(),跳转到setjmp啊,这一步是如何实现的呢?请指教!!!
----------------解决方案--------------------------------------------------------

只要经过 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编辑过]


----------------解决方案--------------------------------------------------------
对的,老兄,那么关于signal()这个函数之所以会出现运行错误好象没有解释啊!!!
----------------解决方案--------------------------------------------------------

我无法确定你是否是因为系统差异的问题,
我对异常处理(setjmp)还算熟悉,
而 signal及 raise 我使用得甚少(事实上我用C那么久我只用了三次,在为你调试程序时的那次也算)。

我的建议是:

在下面程序中插入一个 getchar()
loop:
getchar();
goto loop;

这是因为,若不设置 getchar() 输入可能会导致连 Ctrl+Break 中断信号都无法识别,
至于为什么,我只能说对不起――以我目前的能力无法向您解释,惭愧,
我研究了一晚上查了几本大全都无法找到答案,抱歉。

我只确定若使用 raise 函数向当前程序发送 SIGINT 信号必然可以使 Ctrl+Break 处理函数 handle 响应。

关于跳转:
我此处可以跳转,您的程序至少应该将 int i 定义在 signal 函数前面,因为 signal() 是执行语句,
int i=setjmp(buf); 此句中包含声明 i 变量,应该在当前复合语句中执行语句的前面。

如这样写:
int i;
i = setjmp(buf);
signal(SIGINT, handle);

无法给你满意的答案,我向你道歉。


----------------解决方案--------------------------------------------------------
  相关解决方案