当前位置:首页 > 芯闻号 > 充电吧
[导读]错误处理是任何语言都需要解决的问题,只有不能保证100%的正确运行,就需要有处理错误的机制。异常处理就是其中的一种错误处理方式。1 过程活动记录(Active Record)C语言中每当有一个函数调用

错误处理是任何语言都需要解决的问题,只有不能保证100%的正确运行,就需要有处理错误的机制。异常处理就是其中的一种错误处理方式。

1 过程活动记录(Active Record)

C语言中每当有一个函数调用时,就会在堆栈(Stack)上准备一个被称为AR的结构。 

每当遇到一次函数调用的语句,C编译器都会产生出汇编代码来在堆栈上分配这个AR。例如下面的C代码:

void a(int i)
{
    if(i==0){
        i = 1;
    }
    else
    {
        printf("i = %d n", i);
    }
}

int main(int argc, char** argv)
{
    a(1);
}

123456789101112131415123456789101112131415

2 通过setjmp和longjmp操纵AR,完成任意跳转

那么如何来操纵AR呢,一个可能的方法是,根据局部变量的地址进行推算,例如对于上面的a函数,执行a函数时的当前AR地址就是参数i的地址偏移8个字节,也就是 ((char*)&i) - 8。然而,不同的C编译器,以及不同的硬件平台都会产生不同的AR结构布局,甚至在一些平台上,AR根本不会存放到Stack中。所以这种方式操纵AR是不通用的。

为此,c语言通过库函数的方式提供了操纵AR的统一方法,那就是setjmp和longjmp函数。

int setjmp(jmp_buf jb);
void longjmp(jmp_buf jb, int r);

1212

setjmp用于保存当前AR到jb变量中; 
而longjmp用于设置当前AR为jb,并跳转到调用setjmp();之后的第一个语句处。其结果就相当于回到了setjmp()刚执行完毕,只是偷偷的修改了setjmp的返回值。

setjmp()第一次调用时总是返回0,而通过longjmp(jb,r)跳转后其返回值总是被修改为r,并且r不能为0。这样程序中就很容易根据setjmp()的返回值来判断是否是longjmp()导致了跳转才执行到此。

setjmp/longjmp主要从嵌套的函数调用中跳出来。

#include#includejmp_buf jb;
void a();
void b();
void c();

int main()
{
    if(setjmp(jb)==0){
        a();
    }
    printf("after a(); n");
    return 0;
}
void a()
{
    b();
    printf("a() is calledn");
}
void b()
{
    c();
    printf("b() is calledn");
}
void c()
{
    printf("c() is calledn");
    longjmp(jb, 1);
}

12345678910111213141516171819202122232425262728293031321234567891011121314151617181920212223242526272829303132

在c()中可以直接跳转到main()中,实际上longjmp不限制跳转的目的地,可以跳转到任意位置并恢复当时的堆栈环境(堆栈平衡)。

3 C语言中实现异常处理

异常处理是错误处理的一种方式,C语言中更常用的错误处理方式是检测函数返回值。

#includeint f1()
{
    if(1/*正确执行*/) { return 1; }
    else { return -1; }
}
int f2()
{
    if(0/*正确执行*/) { return 1; }
    else { return -1; }
}

int main()
{
    if(f1()<0){
        printf("错误处理1n");
        exit(1);
    }

    if(f2()<0){
        printf("错误处理2n");
        exit(2);
    }
    return 0;
}

123456789101112131415161718192021222324252627123456789101112131415161718192021222324252627

上面代码显示了常见的C语言错误处理方式。严谨的软件开发中,必须检测每一次函数调用可能出现的错误,并做相应的处理。造成的后果就是冗长繁琐的代码。为了统一处理错误,C++,C#,Java等现代语言引入了异常处理机制。同样功能的C++代码大概如下:

#includeclass Ex1{
};
class Ex2{
};
void f1()
{
    printf("进入f1()n");
    if(0/*正确执行*/){ }
    else {
        throw Ex1();
    }
    printf("退出f1()n");
}
void f2()
{
    printf("进入f2()n");
    if(1/*正确执行*/) {  }
    else {
        throw Ex2();
    }
    printf("退出f2()n");
}

int main()
{
    try{
        f1();
        f2();
    }catch(Ex1 &ex){
        printf("处理错误1n");
        exit(1);
    }
    catch(Ex2 &ex){
        printf("处理错误2n");
        exit(2);
    }
    return 0;
}

123456789101112131415161718192021222324252627282930313233343536373839404142123456789101112131415161718192021222324252627282930313233343536373839404142

程序输出:

进入f1()
处理错误1

1212

可见,异常处理让代码看起来更加整洁,逻辑代码在一起,错误处理代码在一起。throw后面的语句不再执行,执行流直接跳转到最近的try对应的catch块。

可以推测,

throw要负责两件事情:(1)完成跳转;(2)恢复堆栈AR;try则负责保存当前AR

可见这与setjmp/longjmp基本相当。于是可以在C中近似写成。

#include#include#includejmp_buf jb;

void f1()
{
    printf("进入f1()n");
    if(0/*正确执行*/){ }
    else {
        longjmp(jb,1);
    }
    printf("退出f1()n");
}
void f2()
{
    printf("进入f2()n");
    if(1/*正确执行*/) {  }
    else {
        longjmp(jb, 2);
    }
    printf("退出f2()n");
}

int main()
{
    int r = setjmp(jb);
    if(r==0){
        f1();
        f2();
    }else if(r==1){
        printf("处理错误1n");
        exit(1);
    }else if(r==2){
        printf("处理错误2n");
        exit(2);
    }
    return 0;
}

12345678910111213141516171819202122232425262728293031323334353637383940411234567891011121314151617181920212223242526272829303132333435363738394041

当然完整的异常处理远比这里的代码要复杂,需要考虑异常的嵌套等,这里仅仅给出最简单的思路。

4 不要在C++中使用setjmp和longjmp

C++为异常处理提供了直接支持。除非极特殊需要,不要再重新实现自己的异常机制,尤其需要说明的是,简单的调用setjmp/longjmp有可能带来问题。如

#include#include#includeclass MyClass
{
public:
    MyClass(){ printf("MyClass::MyClass()n");}
    ~MyClass(){ printf("MyClass::~MyClass()n");}
};
jmp_buf jb;

void f1()
{
    MyClass obj;
    printf("进入f1()n");
    if(0/*正确执行*/){ }
    else {
        longjmp(jb,1);
    }
    printf("退出f1()n");
}
void f2()
{
    printf("进入f2()n");
    if(1/*正确执行*/) {  }
    else {
        longjmp(jb, 2);
    }
    printf("退出f2()n");
}

int main()
{
    int r = setjmp(jb);
    if(r==0){
        f1();
        f2();
    }else if(r==1){
        printf("处理错误1n");
        exit(1);
    }else if(r==2){
        printf("处理错误2n");
        exit(2);
    }
    return 0;
}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

g++编译,程序输出:

MyClass::MyClass()
进入f1()
处理错误1

123123

vc++编译,程序输出:

MyClass::MyClass()
进入f1()
MyClass::~MyClass()
处理错误1

12341234

longjmp()跳转前局部对象可能并不会析构(g++),也可能析构(VC++),C++标准对此并无明确要求。这种依赖于具体编译器版本的代码是应该避免的。

而C++本身的throw关键字,却能严格保证局部对象构造和析构的成对调用。

5 辩证看待异常处理

为实现异常处理,C++编译器为此必须做更多的工作,也必然导致在AR中直接或间接地存放更多的信息,并产生操作这些信息的汇编代码,最终必然导致运行效率的降低。

另一方面,已经存在大量没有严格使用异常处理C++函数库和类库,兼容的C库更是没有异常的概念,历史的包袱让C++很难完全采用异常处理。在这个方面,Java和C#从头开始,重要的库都实现了标准的异常处理规范,完全采用异常机制切实可行。

有趣的是C++11在标准中删除了异常规范,而且添加了 noexcept关键字来声明一个函数不会抛出异常,可见异常并不是那么受欢迎。

C++编译器也会提供一个禁用异常的选项。 

然而,C++的STL广泛使用异常,所以实际上使用了STL的C++程序是不可能禁用异常的,要是没有了STL,C++又有什么优势了呢?C++在不断的矛盾冲突中向前发展者。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

嵌入式开发作为信息技术领域的重要分支,其涉及的语言种类繁多,各具特色。这些语言的选择取决于目标平台的特性、性能需求、开发者的熟练程度以及项目的具体要求。本文将详细介绍几种常见的嵌入式开发语言,包括C语言、C++、汇编语言...

关键字: 嵌入式开发 C语言

Java语言和C语言是两种不同的编程语言,它们在语法、特性和应用领域上有许多差别。下面将详细介绍Java语言和C语言之间的差异以及它们各自的技术特点。

关键字: Java语言 C语言 编程

嵌入式系统是一种专门设计用于特定应用领域的计算机系统,它通常由硬件和软件组成,并且被嵌入到其他设备或系统中,以实现特定的功能。在嵌入式系统的开发过程中,选择适合的编程语言是至关重要的。C语言是一种被广泛应用于嵌入式系统开...

关键字: 嵌入式 计算机 C语言

C语言是一种广泛应用于软件开发领域的编程语言。它是由贝尔实验室的Dennis Ritchie在20世纪70年代初创建的,旨在为UNIX操作系统的开发提供一种高级编程语言。C语言具有简洁、高效、可移植性强等特点,因此成为了...

关键字: C语言 操作系统 应用程序

嵌入式系统是现代生活中无处不在的一部分。它们包括了我们的家电、汽车、智能手机、医疗设备等等。这些系统的工作必须高效、可靠,因为它们往往控制着生活中的关键方面。而C语言作为一种广泛用于嵌入式系统开发的编程语言,其质量和稳定...

关键字: 嵌入式系统 C语言 编程

在嵌入式系统开发领域中,C语言是使用最广泛的编程语言之一。它具有高效、灵活和可移植的特点,成为嵌入式系统设计师的首选语言。本文将介绍C语言编程的基本概念、特点以及在嵌入式系统开发中的应用。

关键字: 嵌入式系统 C语言 编程

C语言编译器是一种用于将C语言源代码转换为可执行程序的软件工具。它的主要功能是将C语言代码翻译成机器语言,以便计算机能够理解和执行。C语言编译器通常包括预处理器、编译器、汇编器和链接器等多个组件,它们协同工作以完成编译过...

关键字: C语言 编译器 Microsoft Visual C++

Matlab和C语言的区别是:1、用途不同;2、语法不同;3、运行速度不同;4、可移植性不同;5、代码管理不同。Matlab是一种数值计算和科学计算工具

关键字: matlab语言 C语言 系统编程

单片机是一种集成电路,它包含了中央处理器、存储器、输入输出接口和时钟等基本部件。单片机广泛应用于各种电子设备中,如家用电器、汽车电子、医疗设备等。单片机的使用领域已十分广泛,如智能仪表、实时工控、通讯设备、导航系统、家用...

关键字: 单片机编程 单片机 C语言

一直以来,嵌入式都是大家的关注焦点之一。因此针对大家的兴趣点所在,小编将为大家带来嵌入式的相关介绍,详细内容请看下文。

关键字: 嵌入式 C语言
关闭
关闭