• c++ try catch 问题[转]


    try{} catch(…){}
    以前都是用try{} catch(…){}来捕获C++中一些意想不到的异常,今天看了Winhack的帖子才知道,这种方法在VC中其实是靠不住的。例如下面的代码:

    try
    {
    BYTE* pch ;
    pch = ( BYTE* )00001234 ;   //给予一个非法地址
    *pch = 6 ; //对非法地址赋值,会造成Access Violation 异常
    }
    catch(...)
    {
    AfxMessageBox( "catched" ) ;
    }
    这段代码在debug下没有问题,异常会被捕获,会弹出”catched”的消息框。但在Release方式下如果选择了编译器代码优化选项,则VC编译器会去搜索try块中的代码, 如果没有找到throw代码,他就会认为try catch结构是多余的, 给优化掉。这样造成在Release模式下,上述代码中的异常不能被捕获,从而迫使程序弹出错误提示框退出。

    那么能否在release代码优化状态下捕获这个异常呢, 答案是有的。就是__try, __except结构, 上述代码如果改成如下代码异常即可捕获。

    __try
    {
    BYTE* pch ;
    pch = ( BYTE* )00001234 ;   //给予一个非法地址
    *pch = 6 ; //对非法地址赋值,会造成Access Violation 异常
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
    AfxMessageBox( "catched" ) ;
    }
    但是用__try, __except块还有问题,就是这个不是C++标准, 而是Windows平台特有的扩展。 而且如果在使用过程中涉及局部对象析构函数的调用,则会出现C2712 的编译错误。 那么还有没有别的办法呢?

    当然有, 就是仍然使用C++标准的try{}catch(..){},但在编译命令行中加入 /EHa 的参数。这样VC编译器不会把try catch模块给优化掉了。

    找到一篇比较好的英文文章谈这个问题: http://members.cox.net/doug_web/eh.htm

    用C++10 年多了 , 居然这么基础的问题都搞错, 真是汗颜。要加紧学习啊, Stay Hungry, Stay Foolish!

    Written by oldmonk on 九月 11th, 2006 with 2 comments.
    Read more articles on IT.



    http://se.csai.cn/ExpertEyes/No138.htm
                                         C++中catch(…)如何使用
    上一篇文章中详细讲了讲C++异常处理模型的trycatch使用语法,其中catch关键字是用来定义catch block的,它后面带一个参数,用来与异常对象的数据类型进行匹配。注意catch关键字只能定义一个参数,因此每个catch block只能是一种数据类型的异常对象的错误处理模块。如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catch(…)”。

    感性认识

    1、catch(…)到底是一个什么样的东东,先来个感性认识吧!看例子先:

    int main()
    {
    try
    {
    cout << "在 try block 中, 准备抛出一个异常." << endl;
    //这里抛出一个异常(其中异常对象的数据类型是int,值为1)
    throw 1;
    }
    //catch( int& value )
    //注意这里catch语句
    catch( …)
    {
    cout << "在 catch(…) block 中, 抛出的int类型的异常对象被处理" << endl;
    }
    }

      2、哈哈!int类型的异常被catch(…)抓获了,再来另一个例子:

    int main()
    {
    try
    {
    cout << "在 try block 中, 准备抛出一个异常." << endl;
    //这里抛出一个异常(其中异常对象的数据类型是double,值为0.5)
    throw 0.5;
    }
    //catch( double& value )
    //注意这里catch语句
    catch( …)
    {
    cout << "在 catch(…) block 中, double类型的异常对象也被处理" << endl;
    }
    }

      3、同样,double类型的异常对象也被catch(…)块抓获了。是的,catch(..)能匹配成功所有的数据类型的异常对象,包括C++语言提供所有的原生数据类型的异常对象,如int、double,还有char*、int*这样的指针类型,另外还有数组类型的异常对象。同时也包括所有自定义的抽象数据类型。例程如下:

    int main()
    {
    try
    {
    cout << "在 try block 中, 准备抛出一个异常." << endl;
    //这里抛出一个异常(其中异常对象的数据类型是char*)
    char* p=0;
    throw p;
    }
    //catch( char* value )
    //注意这里catch语句
    catch( …)
    {
    cout << "在 catch(…) block 中, char*类型的异常对象也被处理" << endl;
    }
    }


    int main()
    {
    try
    {
    cout << "在 try block 中, 准备抛出一个异常." << endl;
    //这里抛出一个异常(其中异常对象的数据类型是int[])
    int a[4];
    throw a;
    }
    //catch( int value[] )
    //注意这里catch语句
    catch( …)
    {
    cout << "在 catch(…) block 中, int[]类型的异常对象也被处理" << endl;
    }
    }

      4、对于抽象数据类型的异常对象。catch(…)同样有效,例程如下:

    class MyException
    {
    public:
    protected:
    int code;
    };

    int main()
    {
    try
    {
    cout << "在 try block 中, 准备抛出一个异常." << endl;
    //这里抛出一个异常(其中异常对象的数据类型是MyException)
    throw MyException();
    }
    //catch(MyException& value )
    //注意这里catch语句
    catch( …)
    {
    cout << "在catch(…) block中, MyException类型的异常对象被处理" << endl;
    }
    }
    对catch(…)有点迷糊?
    1、究竟对catch(…)有什么迷糊呢?还是看例子先吧!
    void main()
    {
    int* p = 0;

    try
    {
    // 注意:下面这条语句虽然不是throw语句,但它在执行时会导致系统
    // 出现一个存储保护错误的异常(access violation exception)
    *p = 13; // causes an access violation exception;
    }
    catch(...)
    {
    //catch(…)能抓获住上面的access violation exception异常吗?
    cout << "在catch(…) block中" << endl;
    }
    }

      请问上面的程序运行时会出现什么结果吗?catch(…)能抓获住系统中出现的access violation exception异常吗?朋友们!和我们的主人公阿愚一样,自己动手去测试一把!
    结果又如何呢?实际上它有两种不同的运行结果,在window2000系统下用VC来测试运行这个小程序时,发现程序能输出"在catch(…) block中"的语句在屏幕上,也即catch(…) 能成功抓获住系统中出现的access violation exception异常,很厉害吧!但如果这个同样的程序在linux下用gcc编译后运行时,程序将会出现崩溃,并在屏幕上输出”segment fault”的错误信息。

    主人公阿愚有点急了,也开始有点迷糊了,为什么?为什么?为什么同样一个程序在两种不同的系统上有不同的表现呢?其原因就是:对于这种由于硬件或操作系统出现的系统异常(例如说被零除、内存存储控制异常、页错误等等)时,window2000系统有一个叫做结构化异常处理(Structured Exception Handling,SEH)的机制,这个东东太厉害了,它能和VC中的C++异常处理模型很好的结合上(实际上VC实现的C++异常处理模型很大程度上建立在SEH机制之上的,或者说它是SEH的扩展,后面文章中会详细阐述并分析这个久富盛名的SEH,看看catch(…)是如何神奇接管住这种系统异常出现后的程序控制流的,不过这都是后话)。而在linux系统下,系统异常是由信号处理编程方法来控制的(信号处理编程,signal processing progamming。在介绍unix和linux下如何编程的书籍中,都会有对信号处理编程详细的介绍,当然执著的主人公阿愚肯定对它也不会放过,会深入到unix沿袭下来的信号处理编程内部的实现机制,并尝试完善改进它,使它也能够较好地和C++异常处理模型结合上)。

    那么C++标准中对于这种同一个程序有不同的运行结果有何解释呢?这里需要注意的是,window2000系统下catch(…)能捕获住系统异常,这完全是它自己的扩展。在C++标准中并没有要求到这一点,它只规定catch(…)必须能捕获程序中所有通过throw语句抛出的异常。因此上面的这个程序在linux系统下的运行结果也完全是符合C++标准的。虽然大家也必须承认window2000系统下对C++异常处理模型的这种扩展确实是一个很不错的完善,极大得提高了程序的安全性。

    为什么要用catch(…)这个东东?

    程序员朋友们也许会说,这还有问吗?这篇文章的一开始不就讲到了吗?catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常对象更好的控制手段,使开发的软件系统有很好的可靠性。因此一个比较有经验的程序员通常会这样组织编写它的代码模块,如下:

    void Func()
    {
    try
    {
    // 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
    // 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
    }
    catch(DataType1& d1)
    {
    }
    catch(DataType2& d2)
    {
    }
    catch(DataType3& d3)
    {
    }
    // 注意上面try block中可能抛出的DataType1、DataType2和DataType3三
    // 种类型的异常对象在前面都已经有对应的catch block来处理。但为什么
    // 还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和
    // 可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程
    // 序出现意外崩溃的严重后果,而且这在用VC开发的系统上更特别有效,因
    // 为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现
    // 在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个
    // 指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种
    // 潜在的隐患提供了一种有效的补救措施。
    catch(…)
    {
    }
    }

    还有,特别是VC程序员为了使开发的系统有更好的可靠性,往往在应用程序的入口函数中(如MFC框架的开发环境下 CXXXApp::InitInstance())和工作线程的入口函数中加上一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常,如下:

    BOOL CXXXApp::InitInstance()
    {
    if (!AfxSocketInit())
    {
    AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
    return FALSE;
    }

    AfxEnableControlContainer();

    // Standard initialization
    // If you are not using these features and wish to reduce the size
    // of your final executable, you should remove from the following
    // the specific initialization routines you do not need.

    #ifdef _AFXDLL
    Enable3dControls(); // Call this when using MFC in a shared DLL
    #else
    Enable3dControlsStatic(); // Call this when linking to MFC statically
    #endif


    // 注意这里有一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常
    try
    {
    CXXXDlg dlg;
    m_pMainWnd = &dlg;
    int nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {
    // TODO: Place code here to handle when the dialog is
    // dismissed with OK
    }
    else if (nResponse == IDCANCEL)
    {
    // TODO: Place code here to handle when the dialog is
    // dismissed with Cancel
    }
    }
    catch(…)
    {
    // dump出系统的一些重要信息,并通知管理员查找出现意外异常的原因。
    // 同时想办法恢复系统,例如说重新启动应用程序等
    }

    // Since the dialog has been closed, return FALSE so that we exit the
    // application, rather than start the application's message pump.
    return FALSE;
    }

      通过上面的例程和分析可以得出,由于catch(…)能够捕获所有数据类型的异常对象,所以在恰当的地方使用catch(…)确实可以使软件系统有着更好的可靠性。这确实是大家使用catch(…)这个东东最好的理由。但不要误会的是,在C++异常处理模型中,不只有catch(…)方法能够捕获几乎所有类型的异常对象(也许有其它更好的方法,在下一篇文章中主人公阿愚带大家一同去探讨一下),可C++标准中为什么会想到定义这样一个catch(…) 呢?有过java或C#编程开发经验的程序员会发现,在它们的异常处理模型中,并没有这样类似的一种语法,可这里不得不再次强调的是,java中的异常处理模型是C++中的异常处理模型的完善改进版,可它反而没有了catch(…),为何呢?还是先去看看下一章吧,“C++的异常处理和面向对象的紧密关系 ”。也许大家能找到一个似乎合理的原因。



    今天整理过去的学习笔记,无意中看到这个,自己写的,居然大部分都不记得,可能是很少用到吧。干脆帖出来,大家有什么补充的。。。

    1、基础介绍
    try
    {
    //程序中抛出异常
    throw value;
    }
    catch(valuetype v)
    {
    //例外处理程序段
    }
    语法小结:throw抛出值,catch接受,当然,throw必须在“try语句块”中才有效。

    2、深入throw:
    (i)、程序接受到throw语句后就会自动调用析构器,把该域(try后的括号内)对象clean up,然后再进
    入catch语句(如果在循环体中就退出循环)。

    这种机制会引起一些致命的错误,比如,当“类”有指针成员变量时(又是指针!),在 “类的构建器
    ”中的throw语句引起的退出,会导致这个指针所指向的对象没有被析构。这里很基础,就不深入了,提
    示一下,把指针改为类就行了,比如模板类来代替指针,在模板类的内部设置一个析构函数。

    (ii)、语句“throw;”抛出一个无法被捕获的异常,即使是catch(...)也不能捕捉到,这时进入终止函数
    ,见下catch。

    3、深入catch:
    一般的catch出现的形式是:
    try{}
    catch(except1&){}
    catch(except2&){}
    catch(...){} //接受所有异常
    一般都写成引用(except1&),原因很简单,效率。

    问题a:抛出异常,但是catch不到异常怎么办?(注意没有java类似的finally语句)
    在catch没有捕获到匹配的异常的时候,会调用默认的终止函数。可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。

    到这里,可以题个问题:“没有try-catch,直接在程序中"throw;",会怎么样?”


    其他一些技巧:
    4、try一个函数体,形式如下
    void fun(type1,type2) try----try放在函数体后
    {
      函数定义
    }
    catch(typeX){}
    这个用法的效果就相当于:
    void fun()
    {
      try{函数定义}
    }


    5、throw一个函数体,形式如下:
    void fun (); // 能抛出任何类型的异常
    void fun () throw(except1,except2,except3)
                       // 后面括号里面是一个异常参数表,本例中只能抛出这3中异常
    void fun () throw()  // 参数表为空,不能抛出异常

    问题b:假设fun()中抛出了一个不在“异常参数表”中的异常,会怎么样?

    答:调用set_terminate()中设定的终止函数。然而,这只是表面现象,实际上是调用默认的unexpected()函数,然而这个默认的 unexpected()调用了set_terminate()中设定的终止函数。可以用set_unexpected()来设置unexpected, 就像set_terminate()一样的用法,但是在设定了新的“unexpected()”之后,就不会再调用set_terminater中设定的终止函数了。

    这个语法是很有用的,因为在用别人的代码时,不知道哪个地方会调用什么函数又会抛出什么异常,用一个异常参数表在申明时限制一下,很实用。
  • 相关阅读:
    Scala 构造器
    Scala 模式匹配
    Scala class & case class & object & case object 对比
    Scala 数据类型 & 类型转换 & 转换精度
    Scala val 和 var 的区别
    Shell脚本统计词频
    Linux基础命令使用总结
    WebGL学习笔记(七):输入和动画
    javaNIO:通道和文件通道 Channel
    javaNIO:缓冲区 Buffer
  • 原文地址:https://www.cnblogs.com/rooney/p/2660165.html
Copyright © 2020-2023  润新知