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语言
    +关注

    关注

    183

    文章

    7649

    浏览量

    146401
  • 异常
    +关注

    关注

    0

    文章

    23

    浏览量

    9537

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

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

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C语言安全编码指南:MISRA C、CERT C、CWE 与 C Secure 标准对比与Perforce QAC应用详解

    如何编写真正安全的C语言代码?指南涵盖MISRA C、CERT、CWE等国际安全编码标准对比,以及如何借助Perforce QAC自动检测漏洞。推荐给嵌入式/汽车/工业软件的开发者!
    的头像 发表于 01-26 17:38 1220次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>安全编码指南:MISRA <b class='flag-5'>C</b>、CERT <b class='flag-5'>C</b>、CWE 与 <b class='flag-5'>C</b> Secure 标准对比与Perforce QAC应用详解

    讲解C语言代码的实现过程

    重点讲解C语言代码的实现过程,算法的C语言实现过程具有一般性,通过PID算法的C
    发表于 01-21 07:58

    c语言中的代码优化

    。不少高级语言,包括C++,并不对产生的浮点表达式从新排序,由于那是一个至关复杂的过程。须要注意的是,重排序的代码和原来的代码代码上一致并
    发表于 01-12 09:45

    C语言主要特点

    ,源程序短,因此输入程序时工作量少。 2.运算符丰富,C语言的运算符包含的范围很广泛,共有34种运算符。C语言把括号、赋值和强制类型转换等都作为运算符
    发表于 01-05 07:41

    请问C语言中整形溢出会产生哪些异常行为?

    C语言中整形溢出会产生哪些异常行为?
    发表于 12-26 07:05

    C语言中一些令人震惊的结构介绍

    。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码
    发表于 12-23 08:15

    使用setjmp及longjmp函数处理异常

    使用setjmp和longjmp函数:这是一种用于实现非局部跳转的方法,就是在程序中设置一个跳转点,并在某些情况下跳转到该跳转点,从而绕过中间的一些代码或函数。这样可以在某些情况下模拟异常处理的效果
    发表于 12-11 08:00

    C语言C++之间的区别是什么

    处理的问题时抛出异常,并在其他地方捕获和处理这些异常C语言没有内建的
    发表于 12-11 06:23

    C++程序异常处理机制

    运行代码进行分离,使得程序更加模块化;另一方面,C++的异常处理可以不需要异常处理
    发表于 12-02 07:12

    C语言的编程技巧

    一个成员是一个未知大小的数组,适用于动态分配内存并关联一个可变长度的数组。‌ ‌3、匿名结构体和联合体‌:C语言允许在结构体或联合体中定义不带标签的内部结构体或联合体,简化代码结构。 ‌4
    发表于 11-27 06:46

    C语言程序的结构

    ,87LPC764有4KB的Flash ROM,而笔者的程序量只有2KB多点,因而第一个想法是改用C语言作为主要的开发语言,应该不至于导致代码空间不够用。其次,考虑到需要定时功能的模块
    发表于 11-26 08:12

    C语言的分支结构介绍

    1.简单if语句 C语言中的分支结构语句中的if条件语句。 简单if语句的基本结构如下: 代码语言:javascript if(表达式) { 执行
    发表于 11-25 07:48

    C语言特性

    的执行效率和资源利用率有着严苛的要求。C 语言生成的代码简洁紧凑,能够在有限的硬件条件下快速运行,满足嵌入式系统对性能的高要求。例如,在智能家居设备的控制芯片中,C
    发表于 11-24 07:01

    线路保护光纤通道异常处理方法

    通道异常的 常见原因、处理步骤及预防措施 ,帮助运维人员快速定位问题,提升故障处理效率。 广州邮科光纤线路保护系统 一、光纤通道异常的常见表现 当线路保护光纤通道出现
    的头像 发表于 11-17 10:01 1787次阅读
    线路保护光纤通道<b class='flag-5'>异常</b><b class='flag-5'>处理</b>方法

    C语言和单片机C语言有什么差异

    的目标代码短、运行速度高、存储空间小、符合C语言的ANSI标准,生成的代码遵循Intel目标文件格式,而且可与A51汇编语言PL/M51
    发表于 11-14 07:55