博文

第20集 C++中如何兼容并支持C语言中提供的异常处理机制 (2006-01-22 13:33:00)

摘要:  C语言中提供的异常处理机制并不是十分严谨,而且比较杂,功能也非常有限。最常见的除了setjmp与longjmp之外,goto语句在实际编程中也使用很广泛(虽然不建议使用它)。大家现在也都知道,在C++语言中,它并不完全兼容并支持setjmp与longjmp函数的使用。但是  C++语言对待goto语句又将如何呢?

C++语言中如何处理goto语句

  大家知道,在C语言程序中,goto语句被编译成机器指令后,它只对应一条jmp指令。但是在C++语言程序中,goto语句也会这么简单吗?no!为什么这么说呢?因为C++语言是面向对象的语言,如果goto语句只会简单地对应一条jmp指令,那么在许多情况下,这会破坏面向对象的一些特性。例如下面的示例程序,代码如下: #include <stdio.h>
#include <setjmp.h>
#include <stdlib.h> class MyTest
{
public:
MyTest ()
{
printf("构造一个MyTest类型的对象\n");
} virtual ~ MyTest ()
{
printf("析构销毁一个MyTest类型的对象\n");
}
}; void main( void )
{
MyTest myobj0;
{
int error;
MyTest myobj1;
MyTest myobj2;
MyTest myobj3; error = 1; // 注意下面这条goto语句,如果它只是一条简单的jmp指令,
// 那么myobj1,myobj2,myobj3对象将如何被析构销毁呢?
if(error) goto Error;

printf("no error, continue\n");
} Error:
return;
}   请编译运行一下,程序的运行结果如下:
  构造一个MyTest类型的对象
  构造一个MyTest类型的对象
  构造一个MyTest类型的对象
  构造一个MyTest类型的对象
  析构销......

阅读全文(3511) | 评论:2

第19集 setjmp与longjmp机制很难与C++和睦相处(2006-01-22 13:32:00)

摘要:  在《第16集 C语言中一种更优雅的异常处理机制》中,就已经提到过,“setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,请使用C++提供的异常处理机制”。它在MSDN中的原文如下:   setjmp and longjmp do not support C++ object semantics. In C++ programs, use the C++ exception-handling mechanism.   这究竟是为什么?大家知道,C++语言中是基本兼容C语言中的语义的。但是为什么,在C++程序中,唯独却不能使用C语言中的异常处理机制?虽然大家都知道,在C++程序中,实际上是没有必要这么做,因为C++语言中提供了更完善的异常处理模型。但是,在许多种特殊情况下,C++语言来开发的应用程序系统中,可能采用了C语言中的异常处理机制。例如说,一个应用程序由于采用C++开发,它里面使用了C++提供的异常处理机制;但是它可能需要调用其它已经由C语言实现的程序库,而恰恰在这个被复用的程序库中,它也采用了异常处理机制。因此对于整个应用程序系统而言,它不可避免地出现了这种矛盾的局面。并且这种情况是非常多见的,也可能是非常危险的。因为毕竟,“setjmp and longjmp do not support C++ object semantics”。所以,我们非常有必要来了解它究竟为什么不会兼容。

  在本篇文章中,主人公阿愚将和程序员朋友们一起,深入探讨setjmp与longjmp机制,为什么它很难与C++和睦相处?另外还有,如果C++语言来开发的应用程序系统中,不得不同时使用这两种异常处理模型时,又如何来尽可能保证程序系统的安全?

C++语言中使用setjmp与longjmp

  闲话少说,还是看例程先吧!代码如下: // 注意,这是一个C++程序。文件扩展名应该为.cpp或其它等。例如,c++setjmp.cpp
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h> //定义一个测试类
class MyTest
{
public:
MyT......

阅读全文(3167) | 评论:1

第18集 玩转setjmp与longjmp (2006-01-22 13:31:00)

摘要:  通过上两篇文章中,对setjmp与longjmp两个函数的深入研究与分析,相信大家已经和主人公阿愚一样,对C语言中提供的这种异常处理机制的使用方法了如指掌了。请不要骄傲和自满,让我们更上一层楼,彻底玩转setjmp与longjmp这两个函数。

  不要忘记,前面我们得出过结论,C语言中提供的这种异常处理机制,与C++中的异常处理模型很相似。例如,可以定义出类似的try block(受到监控的代码);catch block(异常错误的处理模块);以及可以随时抛出的异常(throw语句)。所以说,我们可以通过一种非常有技巧的封装,来达到对setjmp和longjmp的使用方法(或者说语法规则),基本与C++中的语法一致。很有诱惑吧!

  首先展示阿愚封装的在C语言环境中异常处理框架

  1、首先是接口的头文件,主要采用“宏”技术!代码如下: /*************************************************
* author: 王胜祥 *
* email: <mantx@21cn.com> *
* date: 2005-03-07 *
* version: *
* filename: ceh.h *
*************************************************/
/******************************************************************** This file is part of CEH(Exception Handling in C Language). CEH is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. ......

阅读全文(3323) | 评论:0

第17集 全面了解setjmp与longjmp的使用(2006-01-22 13:30:00)

摘要:  上一篇文章对setjmp函数与longjmp函数有了较全面的了解,尤其是这两个函数的作用,函数所完成的功能,以及将setjmp函数与longjmp函数组合起来,实现异常处理机制时,程序模块控制流的执行过程等。这里更深入一步,将对setjmp与longjmp的具体使用方法和适用的场合,进行一个非常全面的阐述。

  另外请特别注意,setjmp函数与longjmp函数总是组合起来使用,它们是紧密相关的一对操作,只有将它们结合起来使用,才能达到程序控制流有效转移的目的,才能按照程序员的预先设计的意图,去实现对程序中可能出现的异常进行集中处理。

  与goto语句的作用类似,它能实现本地的跳转

  这种情况容易理解,不过还是列举出一个示例程序吧!如下: void main( void )
{
int jmpret; jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(1) longjmp(mark, 1); // 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(2) longjmp(mark, 2); // 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(-1) longjmp(mark, -1); // 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
} return;
}   上面的例程非常地简单,其中程序中使用到了异常处理的机制,这使得程序的代码非常紧凑、清晰,易......

阅读全文(2774) | 评论:0

第16集 C语言中一种更优雅的异常处理机制(2006-01-22 13:29:00)

摘要:  上一篇文章对C语言中的goto语句进行了较深入的阐述,实际上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函数调......

阅读全文(3170) | 评论:4

第15集 C语言中的异常处理机制(2006-01-21 11:39:00)

摘要:  在这之前的所有文章中,都是阐述关于C++的异常处理机制。的确,在C++语言中,它提供的异常处理的模型是非常完善的,主人公阿愚因此才和“异常处理”结下了不解之缘,才有了这一系列文章的基本素材,同时主人公阿愚在自己的编程开发过程中,也才更离不开她,喜欢并依赖于她。

  另外,C++语言中完善的异常处理的模型,也更激发了主人公阿愚更多其它的思考。难道异常处理机制只有在C++语言中才有吗?不是的,绝对不是这样的。实际上,异常处理的机制是无处不在的,它与软件的编程思想的发展,与编程语言的发展是同步的。异常处理机制自身的发展和完善过程,也是并记录了我们在编程思想上和编程方法上的改变、进步和发展的过程和重要的足迹。

  在前面的文章中,早就讲到过,异常处理的核心思想是,把功能模块代码与系统中可能出现错误的处理代码分离开来,以此来达到使我们的代码组织起来更美观、逻辑上更清晰,并且同时从根本上来提高我们软件系统长时间稳定运行的可靠性。那么,现在回过头来看,实际上在计算机系统的硬件设计中,操作系统的总体设计中,早期的许多面向结构化程序设计语言中(例如C语言),都有异常处理的机制和方法的广泛运用。只不过是到了像C++这样面向对象的程序设计语言中,才把异常处理的模型设计到了一个相当理想和完善的程度。下面来看看主人公阿愚对在C语言中,异常处理机制的如何被运用?

goto语句,实现异常处理编程,最初也最原始的支持手段

  1、goto语句,程序员朋友们对它太熟悉了,它是C语言中使用最为灵活的一条语句,由它也充分体现出了C语言的许多特点或者说是优点。它虽然是一条高级语言中提供的语句,但是它一般却直接对应一条“无条件直接跳转的机器指令”,所以说它非常地特别,它引起过许多争议,但是这条语句仍然一直被保留了下来,即便是今天的C++语言中,也有对它的支持(虽然不建议使用它)。goto语句有非常多的用途或优点,例如,它特别适合于在编写系统程序中被使用,它能使编写出来的代码非常简练。另外,goto语句另外一个最重要的作用就是,它实际上是一种对异常处理编程,最初也最原始的支持手段或方法。它能把错误处理模块的代码有效与其它代码分离开来。例程如下(请与第一集文章中的示例代码相比较): void main(int argc, char* argv[])<......

阅读全文(3042) | 评论:0

第14集 再探C++中异常的rethrow (2006-01-21 11:39:00)

摘要:  在相遇篇中的《第5集 C++的异常rethrow》文章中,已经比较详细讨论了异常重新被抛出的处理过程。但是有一点却并没有叙述到,那就是C++异常重新被抛出时(rethrow),异常对象的构造、传递和析构销毁的过程会有哪些变化和不同之处。为了精益求精,力求对每一个细节都深入了解和掌握,下面再全面阐述一下各种不同组合情况下的异常构造和析构的过程。

  大家现在知道,异常的重新被抛出有两种方式。其一,由于当前的catch block块处理不了这个异常,所以这个异常对象再次原封不动地被重新抛出;其二,就是在当前的catch block块处理异常时,又激发了另外一个异常的抛出。另外,由于异常对象的传递方式有三种:传值、传引用和传指针。所以实际上这就导致了有6种不同的组合情况。下面分别阐述之。

异常对象再次原封不动地被重新抛出

  1、首先讨论异常对象“按值传递”的方式下,异常对象的构造、传递和析构销毁的过程有何不同之处?毫无疑问,在异常被重新被抛出时,前面的一个异常对象的构造和传递过程肯定不会被影响,也即“按值传递”的方式下,异常被构造了3次,异常对象被“按值传递”到这个catch block中。实际上,需要研究的是,当异常被重新被抛出时,这个异常对象是否在离开当前的这个catch block域时会析构销毁掉,并且这个异常对象是否还会再次被复制构造?以及重新被抛出的异常对象按什么方式被传递?看如下例程: class MyException
{
public:
MyException (string name="none") : m_name(name)
{
number = ++count;
cout << "构造一个MyException异常对象,名称为:"<<m_name<<":"<<number<< endl;
} MyException (const MyException& old_e)
{
m_name = old_e.m_name;
number = ++count; cout << "拷贝一个MyException异常对象,名称为:"<<m_name<&......

阅读全文(3382) | 评论:0

第13集 C++异常对象三种方式传递的综合比较(2006-01-21 11:38:00)

摘要:  上几篇文章已经分别对C++的异常对象的几种不同的传递方式进行了详细地讨论。它们可以被分为按值传递,按引用传递,以及按指针传递等三种方式,现在该是对它们进行全面盘点总结的时候了。希望这种对比、总结及分析对朋友们理解这三种方式的各种区别有所帮助。   按值传递 引用传递 指针传递 语法 catch(std::exception e) catch(std::exception& e) catch(std::exception* e) 如何抛出异常? ①throw exception()
②exception ex;throw ex;
③throw ex_global; ①throw exception()
②exception ex;throw ex;
③throw ex_global;
①throw new exception(); 异常对象的构造次数 三次 二次 一次 效率 低 中 高 异常对象什么时候被销毁 ①局部变量离开作用域时销毁
②临时变量在catch block执行完毕后销毁
③catch后面的那个类似参数的异常对象也是在catch block执行完毕后销毁 ①局部变量离开作用域时销毁
②临时变量在catch block执行完毕后销毁 异常对象动态地在堆上被创建,同时它也要动态的被销毁,销毁的时机是在catch block块中处理完毕后进行 发生对象切片 可能会 不会 不会 安全性 较低,可能会发生对象切片 很好 低,依赖于程序员的能力,可能会发生内存泄漏;或导致程序崩溃 综合性能 差 好 一般 易使用性 好 好 一般   至此,对C++中的异常处理机制与模型已经进行了非常全面的阐述和分析,包括C++异常的语法,C++异常的使用技巧,C++异常与面向对象的相互关系,以及异常对象的构造、传递和最后析构销毁的过程。

  主人公阿愚现在已经开始有点小有成就感了,他知道自己对她(C++中的异常处理机制)已有了相当深入的了解,并且把她完全当成了一个知己,在自己的编程生涯中再也开始离不开她了。而且他与她的配合已经变得十分的默契,得心应手。但是有时好像还是有......

阅读全文(3156) | 评论:0

第12集 C++的异常对象按指针方式被传递(2006-01-21 11:37:00)

摘要:  上两篇文章分别详细讨论了C++的异常对象按值传递和按引用传递的两种方式,本文继续讨论最后的一种的方式:按指针传递。 异常对象在什么时候构造?

  1、与按值和按引用传递异常的方式相比,在按指针传递异常的方式下,异常对象的构造方式有很大的不同。它必须是在堆中动态构造的异常对象,或者是全局性static的变量。示例程序如下: void main()
{
try
{
// 动态在堆中构造的异常对象
throw new MyMemoryException("ex_obj1");
}
catch(MyException* e)
{
cout<<endl<<"捕获到一个MyException*类型的异常,名称为:"<<e->GetName()<<endl; delete e;
}
}

  2、注意,通过指针方式传递的异常对象不能是局部变量,否则后果很严重,示例如下: void main()
{
try
{
// 局部变量,异常对象
MyMemoryException ex_obj1("ex_obj1"); // 抛出一个指针类型的异常
// 注意:这样做很危险,因为ex_obj1这个对象离开了这个作用域即
// 析构销毁
throw &ex_obj1;
}
catch(MyException* e)
{
// 下面语句虽然不会导致程序崩溃,但是e->GetName()取得的结果
// 也是不对的。
cout<<endl<<"捕获到一个MyException*类型的异常,名称为:"<<e->GetName()<<endl; // 这条语句会导致程序崩溃
// delete e;
}
}

  程序运行的结果是:
  构造一个MyException异常对象,名称为:ex_obj1
  构造一个MyMemoryException异常对象,名称为:ex_obj1
  销毁一个MyMemoryException异常对象,名称为:e......

阅读全文(3098) | 评论:0

第11集 C++的异常对象按引用方式被传递(2006-01-21 11:37:00)

摘要:  上一篇文章详细讨论了C++的异常对象按值传递的方式,本文继续讨论另外的一种的方式:引用传递。 异常对象在什么时候构造?

  其实在上一篇文章中就已经讨论到了,假如异常对象按引用方式被传递,异常对象更应该被构造出一个临时的变量。因此这里不再重复讨论了。

异常对象按引用方式传递

  引用是C++语言中引入的一种数据类型形式。它本质上是一个指针,通过这个特殊的隐性指针来引用其它地方的一个变量。因此引用与指针有很多相似之处,但是引用用起来较指针更为安全,更为直观和方便,所以C++语言建议C++程序员在编写代码中尽可能地多使用引用的方式来代替原来在C语言中使用指针的地方。这些地方主要是函数参数的定义上,另外还有就是catch到的异常对象的定义。

  所以异常对象按引用方式传递,是不会发生对象的拷贝复制过程。这就导致引用方式要比传值方式效率高,此时从抛出异常、捕获异常再到异常错误处理结束过程中,总共只会发生两次对象的构造过程(一次是异常对象的初始化构造过程,另一次就是当执行throw语句时所发生的临时异常对象的拷贝复制的构造过程)。而按值传递的方式总共是发生三次。看看示例程序吧!如下: void main()
{
try
{
{
throw MyException(); } }
// 注意:这里是定义了引用的方式
catch(MyException& e)
{
cout<<"捕获到一个MyException类型的异常,名称为:"<<e.GetName()<<endl;
}
}   程序运行的结果是:
  构造一个MyException异常对象,名称为:none
  拷贝一个MyException异常对象,名称为:none
  销毁一个MyException异常对象,名称为:none
  捕获到一个MyException类型的异常,名称为:none
  销毁一个MyException异常对象,名称为:none   程序的运行结果是不是显示出:异常对象确实是只发生两次构造过程。并且在执行catch block之前,局部变量的异常对象已经被析构销毁了,而属于临时变量的异常对象......

阅读全文(3396) | 评论:0