异常处理与MiniDump详解(1) C++异常

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

讨论新闻组及文件

一、   综述

我很少敢为自己写的东西弄个详解的标题,之所以这次敢于这样,自然还算是有点底气的。并且也以此为动力,督促自己好好的将这两个东西研究透。

当年刚开始工作的时候,第一个工作就是学习breakpad的源代码,然后了解其原理,为公司写一个ExceptionHandle的库,以处理服务器及客户端的未处理异常(unhandle exception),并打下dump,以便事后分析,当年这个功能在有breakpad的示例在前时,实现难度并不大,无非就是调用了SetUnhandledExceptionFilter等函数,让windows在出现未处理异常时让自己的回调函数接管操作,然后利用其struct _EXCEPTION_POINTERS* ExceptionInfo的指针,通过MiniDumpWriteDump API将Dump写下来。但是仍记得,那时看到《Windows 核心编程》第五部分关于结构化异常处理的描述时那种因为得到新鲜知识时的兴奋感,那是我第一次这样接近Windows系统的底层机制,如同以前很多次说过的,那以后我很投入的捧着读完了《Windows 核心编程》,至今受益匪浅。当时也有一系列一边看源代码一边写下心得的时候,想想,都已经一年以前的事情了。

读windows核心编程,结构化异常部分,理解摘要

Breakpad在进程中完成dump的流程描述

Breakpad 使用方法理解文档

直到最近,为了控制服务器在出现异常时不崩溃,(以前是崩溃的时候打Dump),对SEH(windows结构化异常)又进行了进一步的学习,做到了在服务器出现了异常情况(例如空指针的访问)时,服务器打下Dump,并继续运行,并不崩溃,结合以前也是我写的监控系统,通知监控客户端报警,然后就可以去服务器上取回dump,并分析错误,这对服务器的稳定性有很大的帮助,不管我们对服务器的稳定性进行了多少工作,作为C++程序,偶尔的空指针访问,几乎没有办法避免。。。。。。但是,这个工作,对这样的情况起到了很好的缓冲作用。在这上面工作许久,有点心得,写下来,供大家分享,同时也是给很久以后的自己分享。

二、   为什么需要异常

《Windows核心编程》第4版第13章开头部分描述了一个美好世界,即所编写的代码永远不会执行失败,总是有足够的内存,不存在无效的指针。。。。但是,那是不存在的世界,于是,我们需要有一种异常的处理措施,在C语言中最常用的(其实C++中目前最常用的还是)是利用错误代码(Error Code)的形式。

这里也为了更好的说明,也展示一下Error Code的示例代码:

Error Code常用方式:

1.最常用的就是通过返回值判断了:

比如C Runtime Library中的fopen接口,一旦返回NULL,Win32 API中的CreateFiley一旦返回INVALID_HANDLE_VALUE,就表示执行失败了。

2.当返回值不够用(或者携带具体错误信息不够的)时候,C语言中也常常通过一个全局的错误变量来表示错误。

比如C Runtime Library中的errno 全局变量,Win32 API中的GetLastError,WinSock中的WSAGetLastError函数就是这种实现。

既然Error Code在这么久的时间中都是可用的,好用的,为什么我们还需要其他东西呢?

这里可以参考一篇比较浅的文章。《错误处理和异常处理,你用哪一个》,然后本人比较钦佩的pongba还有一篇比较深的文章:《错误处理(Error-Handling):为何、何时、如何(rev#2)》,看了后你一定会大有收获。当pongba列出了16条使用异常的好处后,我都感觉不到我还有必要再去告诉你为什么我们要使用异常了。

但是,这里在其无法使用异常的意外情况下,(实际是《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》一书中所写)

一,     用异常没有带来明显的好处的时候:比如所有的错 误都会在立即调用端解决掉或者在非常接近立即调用端的地方解决掉。

二,     在实际作了测定之后发现异常的抛出和捕获导致了显著的时间开销:这通常只有两种情 况,要么是在内层循环里面,要么是因为被抛出的异常根本不对应于一个错误。

很明显,文中列举的都是完全理论上理想的情况,受制于国内的开发环境,无论多么好的东西也不一定实用,你能说国内多少地方真的用上了敏捷开发的实践经验?这里作为现实考虑,补充几个没有办法使用异常的情况:

一.     所在的项目组中没有合理的使用RAII的习惯及其机制,比如无法使用足够多的smart_ptr时,最好不要使用异常,因为异常和RAII的用异常不用RAII就像吃菜不放盐一样。这一点在后面论述一下。

二.     当项目组中没有使用并捕获异常的习惯时,当项目组中认为使用异常是奇技淫巧时不要使用异常。不然,你自认为很好的代码,会在别人眼里不可理解并且作为异类,接受现实。

三、   基础篇

先回顾一下标准C++的异常用法

1.      C++标准异常

只有一种语法,格式类似:

try

{

}

catch()

{
}

经常简写为try-catch,当然,也许还要算上throw。格式足够的简单。

以下是一个完整的例子:

MyException:

#include <string>

#include <iostream>

using namespace std;

class MyException : public exception

{

public:

MyException(const char* astrDesc)

{

mstrDesc = astrDesc;

}

string mstrDesc;

};

int _tmain(int argc, _TCHAR* argv[])

{

try

{

throw MyException("A My Exception");

}

catch(MyException e)

{

cout <<e.mstrDesc <<endl;

}

return 0;

}

这里可以体现几个异常的优势,比如自己的异常继承体系,携带足够多的信息等等。另外,虽然在基础篇,这里也讲讲C++中异常的语义,

如下例子中,

throwException:

#include <string>

#include <iostream>

using namespace std;

class MyException : public exception

{

public:

MyException(const char* astrDesc)

{

mstrDesc = astrDesc;

}

MyException(const MyException& aoOrig)

{

cout <<"Copy Constructor MyException" <<endl;

mstrDesc = aoOrig.mstrDesc;

}

MyException& operator=(const MyException& aoOrig)

{

cout <<"Copy Operator MyException" <<endl;

if(&aoOrig == this)

{

return *this;

}

mstrDesc = aoOrig.mstrDesc;

return *this;

}

~MyException()

{

cout <<"~MyException" <<endl;

}

string mstrDesc;

};

void exceptionFun()

{

try

{

throw MyException("A My Exception");

}

catch(MyException e)

{

cout <<e.mstrDesc <<" In exceptionFun." <<endl;

e.mstrDesc = "Changed exception.";

throw;

}

}

int _tmain(int argc, _TCHAR* argv[])

{

try

{

exceptionFun();

}

catch(MyException e)

{

cout <<e.mstrDesc <<" Out exceptionFun." <<endl;

throw;

}

return 0;

}

输出:

Copy Constructor MyException

A My Exception In exceptionFun.

~MyException

Copy Constructor MyException

A My Exception Out exceptionFun.

~MyException

可以看出当抛出C++异常的copy语义,抛出异常后调用了Copy Constructor,用新建的异常对象传入catch中处理,所以在函数中改变了此异常对象后,再次抛出原异常,并不改变原有异常。

这里我们经过一点小小的更改,看看会发生什么:

throwAnotherException

#include <string>

#include <iostream>

using namespace std;

class MyException : public exception

{

public:

MyException(const char* astrDesc)

{

mstrDesc = astrDesc;

}

MyException(const MyException& aoOrig)

{

cout <<"Copy Constructor MyException" <<endl;

mstrDesc = aoOrig.mstrDesc;

}

MyException& operator=(const MyException& aoOrig)

{

cout <<"Copy Operator MyException" <<endl;

if(&aoOrig == this)

{

return *this;

}

mstrDesc = aoOrig.mstrDesc;

return *this;

}

~MyException()

{

cout <<"~MyException" <<endl;

}

string mstrDesc;

};

void exceptionFun()

{

try

{

throw MyException("A My Exception");

}

catch(MyException e)

{

cout <<e.mstrDesc <<" In exceptionFun." <<endl;

e.mstrDesc = "Changed exception.";

throw e;

}

}

int _tmain(int argc, _TCHAR* argv[])

{

try

{

exceptionFun();

}

catch(MyException e)

{

cout <<e.mstrDesc <<" Out exceptionFun." <<endl;

throw;

}

return 0;

}

这里和throwException程序的唯一区别就在于不是抛出原有异常,而是抛出改变后的异常,输出如下:

Copy Constructor MyException

A My Exception In exceptionFun.

Copy Constructor MyException

Copy Constructor MyException

~MyException

~MyException

Changed exception. Out exceptionFun.

~MyException

你会发现连续的两次Copy Constructor都是改变后的异常对象,这点很不可理解。。。。。。。因为事实上一次就够了。但是理解C++的Copy异常处理语义就好理解了,一次是用于传入下一次的catch语句中的,还有一次是留下来,当在外层catch再次throw时,已经抛出的是改变过的异常对象了,我用以下例子来验证这点:

throwTwiceException

#include <string>

#include <iostream>

using namespace std;

class MyException : public exception

{

public:

MyException(const char* astrDesc)

{

mstrDesc = astrDesc;

}

MyException(const MyException& aoOrig)

{

cout <<"Copy Constructor MyException" <<endl;

mstrDesc = aoOrig.mstrDesc;

}

MyException& operator=(const MyException& aoOrig)

{

cout <<"Copy Operator MyException" <<endl;

if(&aoOrig == this)

{

return *this;

}

mstrDesc = aoOrig.mstrDesc;

return *this;

}

~MyException()

{

cout <<"~MyException" <<endl;

}

string mstrDesc;

};

void exceptionFun()

{

try

{

throw MyException("A My Exception");

}

catch(MyException e)

{

cout <<e.mstrDesc <<" In exceptionFun." <<endl;

e.mstrDesc = "Changed exception.";

throw e;

}

}

void exceptionFun2()

{

try

{

exceptionFun();

}

catch(MyException e)

{

cout <<e.mstrDesc <<" In exceptionFun2." <<endl;

throw;

}

}

int _tmain(int argc, _TCHAR* argv[])

{

try

{

exceptionFun2();

}

catch(MyException e)

{

cout <<e.mstrDesc <<" Out exceptionFuns." <<endl;

throw;

}

return 0;

}

输出如下,印证了我上面的说明。

Copy Constructor MyException

A My Exception In exceptionFun.

Copy Constructor MyException

Copy Constructor MyException

~MyException

~MyException

Changed exception. In exceptionFun2.

~MyException

Copy Constructor MyException

Changed exception. Out exceptionFuns.

上面像语言律师一样的讨论着C++本来已经足够简单的异常语法,其实简而言之,C++总是保持着一个上次抛出的异常用于用户再次抛出,并copy一份在catch中给用户使用。

但是,实际上,会发现,其实原有的异常对象是一直向上传递的,只要你不再次抛出其他异常,真正发生复制的地方在于你catch异常的时候,这样,当catch时使用引用方式,那么就可以避免这样的复制。

referenceCatch

#include <string>

#include <iostream>

using namespace std;

class MyException : public exception

{

public:

MyException(const char* astrDesc)

{

mstrDesc = astrDesc;

}

MyException(const MyException& aoOrig)

{

cout <<"Copy Constructor MyException: " <<aoOrig.mstrDesc <<endl;

mstrDesc = aoOrig.mstrDesc;

}

MyException& operator=(const MyException& aoOrig)

{

cout <<"Copy Operator MyException:" <<aoOrig.mstrDesc <<endl;

if(&aoOrig == this)

{

return *this;

}

mstrDesc = aoOrig.mstrDesc;

return *this;

}

~MyException()

{

cout <<"~MyException" <<endl;

}

string mstrDesc;

};

void exceptionFun()

{

try

{

throw MyException("A My Exception");

}

catch(MyException& e)

{

cout <<e.mstrDesc <<" In exceptionFun." <<endl;

e.mstrDesc = "Changed exception.";

throw;

}

}

void exceptionFun2()

{

try

{

exceptionFun();

}

catch(MyException e)

{

cout <<e.mstrDesc <<" In exceptionFun2." <<endl;

throw;

}

}

int _tmain(int argc, _TCHAR* argv[])

{

try

{

exceptionFun2();

}

catch(MyException e)

{

cout <<e.mstrDesc <<" Out exceptionFuns." <<endl;

throw;

}

return 0;

}

上例中,使用引用方式来捕获异常,输出如下:

A My Exception In exceptionFun.

Copy Constructor MyException: Changed exception.

Changed exception. In exceptionFun2.

~MyException

Copy Constructor MyException: Changed exception.

Changed exception. Out exceptionFuns.

~MyException

完全符合C++的引用语义。

基本可以发现,做了很多无用功,因为try-catch无非是一层迷雾,其实这里复制和引用都还是遵循着原来的C++简单的复制,引用语义,仅仅这一层迷雾,让我们看不清楚原来的东西。所以,很容易理解一个地方throw一个对象,另外一个地方catch一个对象一定是同一个对象,其实不然,是否是原来那个对象在于你传递的方式,这就像这是个参数,通过catch函数传递进来一样,你用的是传值方式,自然是通过了复制,通过传址方式,自然是原有对象,仅此而已。

另外,最终总结一下,《C++ Coding  Standards》73条建议Throw by value,catch by reference就是因为本文描述的C++的异常特性如此,所以才有此建议,并且,其补上了一句,重复提交异常的时候用throw;

四、   参考资料

  1. Windows核心编程(Programming Applications for Microsoft Windows),第4版,Jeffrey Richter著,黄陇,李虎译,机械工业出版社
  2. MSDN—Visual Studio 2005 附带版,Microsoft
  3. 错误处理和异常处理,你用哪一个,apollolegend
  4. 错误处理(Error-Handling):为何、何时、如何(rev#2),刘未鹏

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

版权声明:本作品由九天雁翎创作,采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。http://www.jtianling.com https://blog.csdn.net/vagrxie/article/details/4317423

异常处理与MiniDump详解(1) C++异常(转)的更多相关文章

  1. 异常处理与MiniDump详解(2) 智能指针与C++异常

    write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一.   综述 <异常处理与MiniDump详解(1) C++异常>稍 ...

  2. 异常处理与MiniDump详解(4) MiniDump

    http://blog.csdn.net/vagrxie/article/details/4398721 异常处理与MiniDump详解(4) MiniDump 分类:             [Wi ...

  3. 异常处理与MiniDump详解(转)

    一.   综述 总算讲到MiniDump了. Dump有多有用我都无法尽数,基本上属于定位错误修复BUG的倚天剑.(日志可以算是屠龙刀)这些都是对于那些不是必出的BUG,放在外面运行的时候出现的BUG ...

  4. 异常处理与MiniDump详解(3) SEH(Structured Exception Handling)

    write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一.   综述 SEH--Structured Exception Handlin ...

  5. 异常处理与MiniDump 用于投放市场c++异常捕获

    最近一段时间,新上线的软件在外场偶尔会出现异常崩溃的情况.由于试用范围比较分散,很难一一前往现场定位问题.而传统的log日志方法,在崩溃的情况下,并不能比较准确的表示出问题位置,这使得软件调试进程缓慢 ...

  6. JAVA基础——异常详解

    JAVA异常与异常处理详解 一.异常简介 什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错.在java中,阻止当前方法或作用域的情况,称之为异常. java中异常的体系是怎么样的呢? 1 ...

  7. Java核心知识体系3:异常机制详解

    1 什么是异常 异常是指程序在运行过程中发生的,由于外部问题导致的运行异常事件,如:文件找不到.网络连接失败.空指针.非法参数等. 异常是一个事件,它发生在程序运行期间,且中断程序的运行. Java ...

  8. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  9. Spring Boot异常处理详解

    在Spring MVC异常处理详解中,介绍了Spring MVC的异常处理体系,本文将讲解在此基础上Spring Boot为我们做了哪些工作.下图列出了Spring Boot中跟MVC异常处理相关的类 ...

随机推荐

  1. http请求数据封装

    package com.wdm.utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java ...

  2. Django models python3搭载mysql

    1    django默认支持sqlite,mysql, oracle,postgresql数据库.  <1> sqlite django默认使用sqlite的数据库,默认自带sqlite ...

  3. amazeui笔记-web组件

    Json.parse()

  4. NDK编程jni学习入门,声明native方法,使其作为java与c的交互接口

    首先,新建工程,简历一个jave类,在其中声明native方法,关键字为native,表面这个方法是从java以为的语言实现. 其次,要实用javac编译此java文件(javac是jdk中的命令,需 ...

  5. Python——基本的方法

    格式化 我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种简便的格式化字符串的方式 >>> 'He ...

  6. NET 知识体系结构

    以下是我根据自身的情况来总结的ASP.NET 知识体系 ASP.NET 知识体系 1.语言C#——C#-知识梳理 2.ASP.NET 3.WinForm 4.ASP.NET MVC 5.EF

  7. [javaSE] GUI(对话框Dialog)

    对话框不能单独存在,依赖于窗体,有显示标题,有模式 获取Dialog对象,new出来,构造参数:Frame对象,String的标题,模式 窗体内部的内容,Label对象,Button对象,调用Dial ...

  8. 十、获取异步线程返回值Callable

    一.简介 异步线程的实现接口Runnable是无法获得返回结果的,而另一个接口Callable可以返回结果.并通过如Future等方式来获取异步结果. 二.代码示例 import java.util. ...

  9. 内嵌Jetty输出debug日志

    Slf4jLog logger = new Slf4jLog(); logger.setDebugEnabled(true); Log.setLog(logger); log4j2.xml中配置如下章 ...

  10. JSP九个内置对象及指令、动作标签

    一.JSP九大内置对象 (一)JSP中无需创建就可以使用的9个对象 输入输出对象 1.response(HttpServletResponse):处理JSP生成的响应,然后将响应结果发送给客户端.是s ...