0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C语言的异常处理案例代码

m3eY_edn_china 来源:未知 作者:易水寒 2017-12-22 08:44 次阅读

相信很多朋友在此之前可能根本没有使用或者听说过C语言的异常处理,印象中都是C++或者java才有的东西,C语言怎么会有异常处理呢?

当然估计在大学出于一般的性的学习考试之类的话老师几乎是不会提及C语言的异常处理的,那么到底什么是异常处理?C语言中又该如何来实现异常处理呢?

那么我们今天就说说一种典型的实现C语言异常处理的方法,以setjmp()函数和longjmp()函数实现的异常处理,我尽可能的把它们是怎样实现异常处理方法讲解清楚,希望接下来的内容对你有所帮助,让你学到一些新的东西。

首先我们来了解下异常处理,异常是一个在程序执行期间发生的事件,它中断正在执行的程序的正常的指令流,而我们的异常处理功能提供了处理程序运行时出现的任何意外或异常情况的方法。

接下来我们先看看setjmp()函数和longjmp()函数实现C语言异常处理。

setjmp()函数原型:

int ( jmp_buf env );

如果我们打开源代码会发现在setjmp()函数中涉及到很多的寄存器的操作,如Ebp、Ebx、Edi、Esi、Esp、 Eip等等,在此就不一一例举了,我们无非是想向读者说明一个问题,那就是在调用setjmp()函数的过程中保存程序的当前运行时的堆栈环境,保存这些堆栈环境有什么用呢?接下来我们看看longjmp()函数。

longjmp()函数原型:

void longjmp( jmp_buf env, int value );

刚刚上面的函数功能是保存程序执行时候的堆栈环境,我们发现在longjmp()函数里也有一个jmp_buf类型的env变量,这其实是为了保证接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且当前的程序控制流,会因此而返回到最初调用setjmp()函数时的程序执行点。此时,在接下来的控制流的例程中,所能访问的所有的变量,包含了longjmp函数调用时所拥有的变量。我们就这样说读者可能就得有点抽象了,那我们还是来看看一段代码后再来分析吧,在此特地给出了一个简单的代码,由易到难的来分析。

[cpp] view plaincopy

#include

#include

jmp_buf buf;

void error_code(void)

{

longjmp(buf,1);

}

int main()

{

double a,b;

printf("请输入被除数:");

scanf("%lf",&a);

printf("请输入除数:");

if(setjmp(buf)==0)

{

scanf("%lf",&b);

if(0==b)

error_code();

printf("相除的结果为:%f\n",a/b);

}

else

printf("出现错误除数为0\n");

return 0;

}

运行结果为:

[cpp] view plaincopy

请输入被除数:12

请输入除数:0

出现错误除数为0

Press any key to continue

看了上面的运行结果,现在我们接着上面的讲,在一开始的部分我们并没有具体的交代setjmp()函数和longjmp()函数的返回值和参数的具体含义。两个函数中的env变量保存的是调用setjmp()函数的时候当前运行程序的堆栈信息,而longjmp()函数的调用就是根据在调用setjmp()函数的时候的堆栈信息返回到最初调用setjmp()函数的地方,而其中的第二个参数就是此刻setjmp()函数的返回值,但是值得注意的就是调用longjmp()函数之后setjmp函数返回的值必须是非零值,如果longjmp传送的value参数值为0,那么实际上setjmp返回的值是1。一开始我们调用setjmp()函数的时候,它的返回值为0,之后再调用longjmp()函数的时候,通过设定longjmp()函数的第二个参数来设定它的返回值。

现在我们来分析上边的代码,在main()函数中,我们最初调用setjmp()函数的时候,把当前的环境信息保存在了buf中,函数返回0,然后往下运行,我们输入0。通过if语句发现b的值为0那么就调用error_code()函数来进行处理,在该函数中我们使用了longjmp()函数,其使用方式为longjmp(buf,1);,通过上面的讲解,我们知道第一个参数的作用是用来得到最初调用setjmp()函数是的环境信息,以便在使用longjmp()函数的时候能够正确的返回到setjmp()函数最初的调用处,而后面的参数表示的返回到setjmp()函数的时候的返回值。我们在此返回1,所以执行else部分的语句。

分析完了上面的代码,读者应该都知道了两个函数的使用方法,值得注意的地方就是我们在setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。否则,假如在setjmp调用之前,执行longjmp函数,将导致程序的执行流变的不可猜测,很轻易导致程序崩溃而退出。为了加深读者的对于两个函数参数的使用,我们看看下面的代码:

[cpp] view plaincopy

#include

#include

#include

#include

jmp_buf buf;

void func1()

{

longjmp(buf,1);

}

void func2()

{

longjmp(buf,2);

}

void func3()

{

longjmp(buf,3);

}

int main( void )

{

int value;

char str[50];

value = setjmp( buf );

if( value == 0 )

{

func1();

}

switch( value )

{

case 1:

strcpy( str, "func1 return value" );

break;

case 2:

strcpy( str, "func2 return value" );

break;

case 3:

strcpy( str, "func3 return value" );

break;

default:

strcpy( str, "Other error value" );

break;

}

printf("%s:%d\n",str,value);

if(1==value)

{

func2();

}

if(2==value)

{

func3();

}

return 0;

}

运行结果为:

[cpp] view plaincopy

func1 return value:1

func2 return value:2

func3 return value:3

Press any key to continue

看看运行结果,我们分析下代码,在每个函数中我们调用longjmp()函数,通过设置第二个参数为不同的值来改变setjmp()函数的返回值,然后我们通过判断value值来打印出是那个函数的返回值,我们在此例举这个简单的代码是要大家加深对于这两个函数的参数的使用情况。如果我们在上面的代码中稍作修改,在setjmp()函数的调用之前调用longjmp()函数,我们发现此时没有任何的输出,程序直接崩溃掉退出了。

接下来我们来看看一个函数的使用,如果对于这个函数不理解的读者,可以多看几次我给出的模拟该函数的实现代码。

头文件: #include

功能:设置某一信号的对应动作

函数原型:void (*signal(int signum,void(* handler)(int)))(int);

注意:第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。

如果读者是第一场接触上面的函数的话可能有些不知道该如何着手,一时间有些难以理解,不知道到底是什么意思。别急,我们现在来逐一分析它到底是什么意思,我们在讲解之前再来看看它的另外一种表示方法。

typedef void(*sig_t) ( int );

sig_t signal(int signum,sig_t handler);

把上面的函数原型拆分为了如上两行代码,现在我们分析下上面的两行代码。

第一行代码定义了一个函数指针(注:如果有对函数指针知识点不熟悉的读者可以去阅读我之前写的那篇文章《C语言的那些小秘密之函数指针》),其类型为含有一个int型参数,无返回值;

第二行代码中,signal函数的返回值是一个函数指针,与第一行我们定义的类型相同,第二个参数也为一个函数指针,其实signal的返回值就是第二个函数指针指向的函数地址。这样说可能有不少读者都有些懵的感觉,还是老方法,代码最有说服力,我们还是为读者模拟下signal的实现方式,呈现出一段代码来分析下。

[cpp] view plaincopy

#include

#include

typedef void (*pfun) ();

pfun signal_call(int a,pfun fdsa);

pfun signal_call(int a,pfun fdsa)

{

return fdsa;

}

void func()

{

printf("hello world!!!\n");

}

int main()

{

pfun p = func;

signal_call(1,p)();

return 0;

}

运行结果为:

[cpp] view plaincopy

hello world!!!

Press any key to continue

现在我们来分析下上面的代码,我们采用上面的定义形式实现了如下两行代码:

typedef void (*pfun) ();

pfun signal_call(int a,pfun fdsa);

在接下来的main()函数中我们定义了一个函数指针p,使其指向了 func()函数,接下来我们使用了一句 signal_call(1,p)();代码,实现了func函数调用,那么这到底是怎么实现的呢?那么我们来分析下,前面的signal_call(1,p)返回的是一个函数指针,在代码中我们发现其实返回的就是p,所以signal_call(1,p)();就可以变形为p(),看到这种形式我们这就可以很清楚的看出,它调用的就是我们代码中的func()函数了。现在读者明白了signal()函数的实现方法,接下来我们来看看一段使用signal捕捉除数为0时候的异常代码。

cpp] view plaincopy

#include

#include

#include

#include

#include

#include

jmp_buf buf;

int err;

void handler( int num )

{

err = num;

printf( "发生浮点计算异常\n");

longjmp( buf, 1);

}

int main( void )

{

double a, b;

char str[20];

int ret;

_control87( 0, _MCW_EM );

if( signal( SIGFPE, handler ) == SIG_ERR )

{

printf("绑定失败\n" );

abort();

}

ret = setjmp( buf );

if(0 == ret )

{

printf("请输入被除数:");

scanf("%lf",&a);

printf("请输入除数:");

scanf("%lf",&b);

printf( "a / b = %4.3g\n", a/b);

printf("发生异常时候不会被执行的语句\n");

}

return 0;

}

没有发生异常时候的运行结果:

[cpp] view plaincopy

请输入被除数:123

请输入除数:3

a / b = 41

发生异常时候不会被执行的语句

Press any key to continue

发生异常时候的运行结果:

[cpp] view plaincopy

请输入被除数:12

请输入除数:0

发生浮点计算异常

Press any key to continue

现在来分析下上面的运行结果,先看看_control87( 0, _MCW_EM );这句,可能很多读者对于这代码比较陌生,它的功能是开启所有的浮点计算异常,通常情况下浮点计算异常是被屏蔽掉的,我们为了能够使得接下来的signal能够捕捉到浮点计算异常,所以要将其开启。在往下看我们通过signal( SIGFPE, handler )来绑定了一个浮点计算异常处理函数,如果发生异常时,那么就调用handler()函数来处理。接下来通过ret = setjmp( buf );保存程序运行的环境信息,以便接下来的调用longjmp()函数能够根据这个保存的信息返回该程序先前setjmp()函数的执行点。同时我们对比两次运行的结果发现如果发现异常的时候接下来的打印语句“printf("发生异常时候不会被执行的语句\n");”是不会被执行的,直接跳转到我们绑定的handler()函数执行了,当然我们在此仅仅是例举一些简单的代码教会读者学会使用setjmp()函数和longjmp()函数来实现异常处理,读者完全可以在此基础上编写出复杂的异常处理。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7530

    浏览量

    128577
  • 异常
    +关注

    关注

    0

    文章

    21

    浏览量

    9197

原文标题:嵌入式C小秘密之你不知道的异常处理

文章出处:【微信号:edn-china,微信公众号:EDN电子技术设计】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    介绍C语言中错误处理异常处理的一些常用的方法和策略

    C语言是一种低级的、静态的、结构化的编程语言,它没有提供像C++或Java等高级语言中的异常处理机制,例如try-catch-finally
    的头像 发表于 02-28 14:25 238次阅读

    如何解决C语言中的“访问权限冲突”异常?C语言引发异常原因分析

    如何解决C语言中的“访问权限冲突”异常?C语言引发异常原因分析  在C语言中,访问权限冲突异常
    的头像 发表于 01-12 16:03 1028次阅读

    Service层的异常处理

    一般初学者学习编码和[错误处理]时,先知道[编程语言]有一种处理错误的形式或约定(如Java就抛异常),然后就开始用这些工具。但却忽视这问题本质:「
    的头像 发表于 01-08 11:29 251次阅读

    直流屏系统的操作、查询及异常处理方法

    直流屏系统的操作、查询及异常处理方法
    的头像 发表于 12-15 10:29 384次阅读
    直流屏系统的操作、查询及<b class='flag-5'>异常</b><b class='flag-5'>处理</b>方法

    C语言有哪些预处理操作?

    C语言的预处理是在编译之前对源代码进行处理的阶段,它主要由预处理器完成。预处理器是一个独立的程序
    的头像 发表于 12-08 15:40 290次阅读
    C<b class='flag-5'>语言</b>有哪些预<b class='flag-5'>处理</b>操作?

    C语言怎样处理json文件?

    获取到的JSON文件,怎样通过C语言进行处理,因为单片机里面只能用C语言,有没有C
    发表于 11-01 06:16

    C语言中ASCII代码是什么意思?

    C语言中ASCII代码是什么意思常见的ASCII代码都需要记吗
    发表于 10-25 07:10

    IAR在进行C语言代码开发的时候怎么嵌入汇编代码

    IAR在进行C语言代码开发的时候怎么嵌入汇编代码
    发表于 10-11 06:45

    如何有效的处理空指针异常

    地遇到这个问题。 那么我们应该如何有效且优雅的处理空指针异常呢? 下面了不起将详细的介绍这个处理方案。 1、什么是空指针异常? 空指针异常
    的头像 发表于 09-30 10:25 1007次阅读

    异常处理和错误码管理

    前言 在业务逻辑中,通常使用两种方式处理异常: 返回错误码:优点是性能更好,但是不宜维护。 抛出异常:可以使得代码更清晰,可读性更好,更符合面向对象。 选择哪种需要根据场景而定,不管如
    的头像 发表于 09-25 14:51 354次阅读
    <b class='flag-5'>异常</b><b class='flag-5'>处理</b>和错误码管理

    arm处理器的异常模式包括哪些

    arm处理器的异常模式包括哪些 ARM处理器的异常模式是其操作系统和应用程序中最重要的方面之一。异常就是
    的头像 发表于 09-05 16:22 2307次阅读

    _C_异常处理_第2节

    C++代码
    充八万
    发布于 :2023年08月11日 12:05:03

    C语言处理命令有哪些?

    不止。先看几个个常识性问题: A) 预处理C 语言的一部分吗? B) 包含“#”号的都是预处理吗? C) 预
    发表于 06-25 06:15

    怎么快速把汇编代码和C语言代码对应起来呢?

    分析软件缺陷,有时候需要把ELF文件反汇编为汇编代码,然后分析汇编代码,需要把汇编代码和C语言代码对应起来。
    的头像 发表于 06-12 16:45 1755次阅读

    Python中的异常机制(二)

     [as  e]:  异常处理语句块 注意事项: try 块包含着可能引发异常代码, except 块则用来捕捉和处理发生的
    的头像 发表于 05-11 18:20 730次阅读