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

    文章

    7616

    浏览量

    138001
  • 异常
    +关注

    关注

    0

    文章

    23

    浏览量

    9273

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

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

收藏 人收藏

    评论

    相关推荐

    代码加密、源代码防泄漏c/c++与git服务器开发环境

    代码加密对于很多研发性单位来说是至关重要的,当然每家企业的业务需求不同所用的开发环境及开发语言也不尽相同,今天主要来讲一下c++及git开发环境的源代码防泄密保护方案。企业源
    的头像 发表于 02-12 15:26 199次阅读
    源<b class='flag-5'>代码</b>加密、源<b class='flag-5'>代码</b>防泄漏<b class='flag-5'>c</b>/<b class='flag-5'>c</b>++与git服务器开发环境

    分析C语言代码结构的设计问题

    来分析一个C语言代码结构的设计问题。 这段代码,使用了两次malloc,分别给 p1 和 p2 申请了内存。用完后,内存释放,防止内存泄漏。 大家觉得,这样的
    的头像 发表于 02-11 09:31 74次阅读

    AKI跨语言调用库神助攻C/C++代码迁移至HarmonyOS NEXT

    量;某知名社交电商平台使用后减少了50%以上跨语言调用接口代码量;某图像处理软件所有C++代码复用通过AKI来实现。使用AKI后这些项目不仅
    发表于 01-02 17:08

    PLLATINUMSIM-SW是否有相关C语言代码进行参考?

    PLLATINUMSIM-SW是否有相关C语言代码进行参考,以达到对器件指标的准确评估直观显示。
    发表于 11-11 06:20

    TMS320LF240x DSP的C语言和汇编代码快速入门

    电子发烧友网站提供《TMS320LF240x DSP的C语言和汇编代码快速入门.pdf》资料免费下载
    发表于 10-18 10:14 1次下载
    TMS320LF240x DSP的<b class='flag-5'>C</b><b class='flag-5'>语言</b>和汇编<b class='flag-5'>代码</b>快速入门

    hex文件怎么能转回去c语言

    将 .hex 文件直接“转回去”为原始的C语言代码是不可能的,因为 .hex 文件是编译后的二进制文件,它包含了机器码,这些机器码是处理器可以直接执行的指令,与原始的
    的头像 发表于 09-02 10:46 2687次阅读

    hex文件如何查看原c语言代码

    处理器可以直接执行的指令,而 C 语言代码则是人类可读的高级编程语言代码。 然而,如果你想要从
    的头像 发表于 09-02 10:37 2910次阅读

    TI C64x+ DSP内核异常处理机制的应用

    电子发烧友网站提供《TI C64x+ DSP内核异常处理机制的应用.pdf》资料免费下载
    发表于 08-28 11:54 0次下载
    TI <b class='flag-5'>C</b>64x+ DSP内核<b class='flag-5'>异常</b><b class='flag-5'>处理</b>机制的应用

    Panasonic松下焊接电异常处理

    电子发烧友网站提供《Panasonic松下焊接电异常处理.pdf》资料免费下载
    发表于 08-19 14:24 0次下载

    嵌入式C编程常用的异常错误处理

    (Exception Handling) 虽然C语言本身不支持异常处理,但可以通过结构化的错误处理机制来模拟
    发表于 08-06 14:32

    一站式统一返回值封装、异常处理异常错误码解决方案—最强的Sping Boot接口优雅响应处理

    1. 前言 统一返回值封装、统一异常处理异常错误码体系的意义在于提高代码的可维护性和可读性,使得代码更加健壮和稳定。统一返回值封装可以避免
    的头像 发表于 06-20 15:42 667次阅读

    C语言内存泄漏问题原理

    内存泄漏问题只有在使用堆内存的时候才会出现,栈内存不存在内存泄漏问题,因为栈内存会自动分配和释放。C语言代码中堆内存的申请函数是malloc。
    发表于 03-19 11:38 585次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>内存泄漏问题原理

    C语言#define的应用

    C/C++ 编程语言中,当程序被编译时,被发送到编译器,编译器将程序转换为机器语言,然后完成编译并执行该程序。预处理器也称为宏预
    发表于 03-06 11:29 436次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>#define的应用

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

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

    C语言中的错误处理机制解析

    C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。
    的头像 发表于 02-26 11:19 579次阅读