C语言的异常机制 setjump longjump函数【转】

用C语言有一段时间, 但是对于setjump 和 longjump 还真的一无所知,汗,
转几篇帖子供学习参考,

转自:zhengrongyang.spaces.live.com
C语言的异常机制 setjump longjump函数
C语言的异常机制
晚上回家翻vckbase的帖子,看到这么一段:

usr_root : c99支持异常吗?
周星星 : 当然不
usr_root : 为什么是当然不?异常机制和c无缘?
七猫 : 当然,到现在还有很多人不喜欢异常。连有些C++的标准库都支持不带异常的。
周星星 : :)俺就是这样的人
usr_root : 哦,我是你的影子

汗!?为了C语言偶不得不说几句申一下冤,真是比窦娥还冤:C语言中,标准库函数setjmp和longjmp形成了结构化异常工具的基础。简单的说即setjmp实例化异常处理程序,而longjmp产生异常。举个例子如下:


/* -------------------------------------------------------------------------
** File : cexcept.c *
** Coder: Spark Song. *
** Note : Use the example code from 《C Interfaces and Implementations》 *
** -------------------------------------------------------------------------
*/

#include
#include
#include
#include

int Allocation_handled = 0;
jmp_buf Allocate_Failed;

void *allocate(unsigned n)
{
void * new = (void *)malloc(n);

if (new)
return new;

if (Allocation_handled) /* 如果实例化了异常处理程序的话... */
longjmp(Allocate_Failed, 1); /* 产生异常,并抛出 */

assert(0); /* 如果没有实例化异常处理程序,则此断言会报出运行期的错误 */
}


int main(int argc, char *argv[])
{
char *buf = 0;
int count = 0;


Allocation_handled = 1; /* 设置实例化异常的标志,设为1表示实例化了异常处理程序 */
if (setjmp(Allocate_Failed)) /* 实例化异常 */
{
fprintf(stderr, "EXCEPT: Couldn't allocate the buffer\n");
exit(EXIT_FAILURE);
}

while(1) /* 测试代码:一直分配内存,直到没有内存为止。没有内存时会触发异常 */
{
buf = (char *)allocate(4096000);
printf("Allocate successs, the count is: %d\n", count++);
}

Allocation_handled = 0;
return 0;
}

上面这个例子在MingW下通过,编译时使用了-std=c89 -pedantic的编译开关(强制使用c89的语法检查)和-std=iso9899:199409? -pedantic(强制使用c99的语法检查)。运行结果如下:

Allocate successs, the count is: 1
... ...
Allocate successs, the count is: 447
Allocate successs, the count is: 448
Allocate successs, the count is: 449
Allocate successs, the count is: 450
EXCEPT: Couldn't allocate the buffer

简要讲述一下代码的流程:setjmp用来实例化异常处理程序,在这里我们的异常处理程序就是往stderr输出一个字符串并退出应用程序。setjmp会返回2次值(颇有些fork()的味道)。setjmp第一次返回值是在应用代码(这里就是main函数里面)调用setjmp的地方,这时候它实例化了异常处理程序,并返回0,所以异常处理程序的代码并没有被执行。在allocate中调用longjmp的时候,会引起setjmp第二次值的返回,此时的返回值由longjmp的第二个参数所决定。文中我们调用longjmp的时候,传给它的第二个参数是 1,所以setjmp返回时会执行if中的异常处理程序。

这个例子就是最最简单的C语言处理异常的原型,我们完全可以利用它来构造出一整套的异常处理体系,一点也不比C++之类的高级语言差。为什么不把异常加入语言本身?我想这是由C语言的设计理念和设计目的决定的。C语言是面向底层和系统开发的较低级的语言,所以语言本身并不复杂,强大的功能完全可以通过函数库来实现。

欲更深入的了解C语言的异常处理体系的设计,可以参考David R.Hanson的C Interfaces and Implementations(中文版《C语言接口与实现》)。


转自: http://topic.csdn.net/u/20090701 ... 8-898f035d37bf.html
原创代码,不足之处请见凉

C/C++ code

/*error.h*/ #ifndef _ERROR_H #define _ERROR_H #include #include #define TRY ErrNum = setjmp(Resume);if(0 == ErrNum) #define CATCH(n) else if((0 != ErrNum) && (((n) == ErrNum) || ((n) == ALL_ERROR))) #define THROW(n) longjmp((Resume),(n)) #define GET_ErrorString(n) errString[n] typedef enum { ERROR_NONE, ERROR_TEST, MEMORY_OFFER, DIVSION_BY_ZERO, OUT_OF_RUN, /* 加入其它的错误号 */ ALL_ERROR, }ERROR_E; const char* errString[]= { "", "Error Test", "Not enough memory", "Divsion by zero", "Out of run", /* 加入错误号所对应的错误信息 */ }; int ErrNum = 0; jmp_buf Resume = {0}; #endif /*_ERROR_H*/ /*main.c*/ #include #include #include "error.h" extern int ErrNum; int main(void) { int n = 0; printf("Input N:"); scanf("%d",&n); TRY { int j = 0; if(0 >= n) { THROW(ERROR_TEST); } else if(n > 0 ) { printf("N is:%d\n",n); } while (1) { j++; if(j > 100000) THROW(OUT_OF_RUN); } } CATCH(ALL_ERROR) { printf("%s,ErrNum:%d!\n",GET_ErrorString(ErrNum),ErrNum); exit(ErrNum); } }



1.输入小于等于0的值
Input N:0
Error Test,ErrNum:1!
2.输入大于0的值
Input N:1
N is:1
Out of run,ErrNum:4!


注意:上面的.h文件是C中TRY...CATCH的实现,main函数是TRY...CATCY的一个例子

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

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

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

setjmp函数有何作用?

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

int setjmp( jmp_buf env );

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

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

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

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

  好了,现在已经对setjmp有了很感性的了解,暂且不做过多评论,接着往下看longjmp函数。

longjmp函数有何作用?

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

void longjmp( jmp_buf env, int value );

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

  longjmp函数用于恢复先前程序中调用的setjmp函数时所保存的堆栈环境。setjmp和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”("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++程序中,小心对setjmp和longjmp的使用,应为setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,使用C++提供的异常处理机制将会更加安全。
把setjmp和longjmp组合起来,原来它这么厉害!

#include
#include
jmp_buf buf;

void banana()
{
printf("in banana\n");
longjmp(buf,1);
printf("You will never see this, because i longjmped\n");
}
int main()
{
if(setjmp(buf))
printf("back in main\n");
else
{
printf("first time in through\n");
banana();
}
return 0;
}

/*
输出:
first time in through
in banana
back in main
*/





不用OS,俺也能实现多任务切换。算是个“开裆裤”吧

#include
jmp_buf jumper0,jumper1,jumper2,jumper3;
void Task0()
{
static int n=0;
if(setjmp(jumper0)>0)
{
while(1)
{
if(setjmp(jumper0)==0) longjump(jumper1,1); //任务切换
n++; //一段任务代码
if(setjmp(jumper0)==0) longjump(jumper1,1); //任务切换
n++; //一段任务代码
}
}

}
void Task1()
{
static int n=0;
if(setjmp(jumper1)>0)
{
while(1)
{
if(setjmp(jumper1)==0) longjump(jumper2,1);
n++;
if(setjmp(jumper1)==0) longjump(jumper2,1);
n++;
}
}

}
void Task2()
{
static int n=0;
if(setjmp(jumper2)>0)
{
while(1)
{
if(setjmp(jumper2)==0) longjump(jumper3,1);
n++;
if(setjmp(jumper2)==0) longjump(jumper3,1);
n++;
}
}

}
void Task3()
{
static int n=0;
if(setjmp(jumper3)>0)
{
while(1)
{
if(setjmp(jumper3)==0) longjump(jumper0,1);
n++;
if(setjmp(jumper3)==0) longjump(jumper0,1);
n++;
}
}

}
void InitTask()
{
Task0();
Task1();
Task2();
Task3();
}
main()
{
InitTask();
longjmp(jumper0,1)
}

//以上代码在keil c中调试通过
//非占先式任务切换
//任务内变量必须是静态的,子程序不用
//任务内不要用寄存器变量

转自: http://www.52rd.com/blog/Detail_RD.Blog_hecrics_16066.html
用了那么久C,第一次看到setjump和longjump,是不是很弱?
hecrics 发表于 2008-9-9 14:36:00

以前从来没看到过,更别说用了,是不是大家都很少用呢?
int setjmp( jmp_buf env );
void longjmp( jmp_buf env, int value );

# setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf 对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump 的上下文,setjmp()返回0 值。对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;
# 以后调用longjmp(j,r)的效果就是一个“长跳转”到由j 描述的上下文处(也就是到那原来设置j 的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r 或1(如果r 设为0 的话)。(记住,setjmp()不能在这种情况时返回0。
  
通常, 用longjmp()来终止异常,用setjmp()标记相应的异常处理程序, 在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料。
  在使用longjmp时,请遵守以下规则或限制:
$ 不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过setjmp所返回的控制流中,例程中寄存器类型的变量将不会被恢复。
$ 不要使用longjmp函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用_fpreset函数,来首先初始化浮点数包后,它是可以通过longjmp来实现从中断处理例程中返回。
$ 在C++程序中,小心对setjmp和longjmp的使用,应为setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,使用C++提供的异常处理机制将会更加安全。
#include
#include
void RaiseException ( jmp_buf jmpbuf)
{
printf( "Press a key to restore stack environment...\n" ) ;
getch() ;
longjmp(jmpbuf, 1);
}

int main()
{
jmp_buf jmpbuf ;
int result ;

printf( "Save stack environment...\n" ) ;
result = setjmp(jmpbuf) ;
if( result == 0 )
{
//Do something
//If anything wrong.
RaiseException(jmpbuf) ;
}
else// the exception handler, return by longjump, non-zero value
{
printf( "longjump() returned %d.\n", result ) ;
exit(0) ;
}

return 0 ;
}
程序输出将是如下序列:
Saving stack environment...
Call MyFunc()...
Press a key torestore stack environment...
setjmp() returned 1
//Example 2
#include
#include

jmp_buf save;

void main()
{
char c;

for (;; )
{
switch ( setjmp( save ))
{
case 0: /* This is the result from setting up. */
printf ( "Zero returned from setjmp on setup.\n\n");
break;
case 1:
printf ( "NORMAL PROGRAM OPERATION\n\n" );
break;
case 2:
printf ( "WARNING\n\n" );
break;
case 3:
printf ( "FATAL ERROR PROGRAM TERMINATED\n\nReally Terminate? y/n: " );
fflush ( stdout );
scanf ( "%1s", &c );
c = tolower ( c );
if ( c == 'y' ) return ( 1 );
printf ( "\n" ); break;
default:
printf ( "Should never return here.\n" );
break;
}
process ();
}
}

void process ()
{
int i;

printf ( "Input a number to simulate an error condition: " );
fflush ( stdout ); scanf ( "%d", &i ); i %= 3;
i++; /* So that we call longjmp with 0 < i < 4 */
longjmp ( save, i);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值