1. 程式人生 > >異常處理與MiniDump詳解 1 C++異常

異常處理與MiniDump詳解 1 C++異常

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

異常處理與MiniDump詳解(1) C++異常

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

討論新聞組及檔案

一、  
綜述

我很少敢為自己寫的東西弄個詳解的標題,之所以這次敢於這樣,自然還算是有點底氣的。並且也以此為動力,督促自己好好的將這兩個東西研究透。

當年剛開始工作的時候,第一個工作就是學習breakpad的原始碼,然後瞭解其原理,為公司寫一個ExceptionHandle的庫,以處理伺服器及客戶端的未處理異常(unhandle exception),並打下dump,以便事後分析,當年這個功能在有breakpad的示例在前時,實現難度並不大,無非就是呼叫了SetUnhandledExceptionFilter等函式,讓windows在出現未處理異常時讓自己的回撥函式接管操作,然後利用其struct _EXCEPTION_POINTERS* ExceptionInfo

的指標,通過MiniDumpWriteDump APIDump寫下來。但是仍記得,那時看到《Windows 核心程式設計》第五部分關於結構化異常處理的描述時那種因為得到新鮮知識時的興奮感,那是我第一次這樣接近Windows系統的底層機制,如同以前很多次說過的,那以後我很投入的捧著讀完了《Windows 核心程式設計》,至今受益匪淺。當時也有一系列一邊看原始碼一邊寫下心得的時候,想想,都已經一年以前的事情了。

windows核心程式設計,結構化異常部分,理解摘要

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

Breakpad 使用方法理解文件

直到最近,為了控制伺服器在出現異常時不崩潰,(以前是崩潰的時候打

Dump),對SEHwindows結構化異常)又進行了進一步的學習,做到了在伺服器出現了異常情況(例如空指標的訪問)時,伺服器打下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中的GetLastErrorWinSock中的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;

    }