Linux 下 C++程序的異常處理技巧

   處理 C++ 中的異常會在語言級別上遇到少許隱含限制,但在某些情況下,您可以繞過它們。學習各種利用異常的方法,您就可以生產更可靠的應用程序。

  保留異常來源信息

  在 C++中,無論何時在處理程序內捕獲一個異常,關於該異常來源的信息都是不爲人知的。異常的具體來源可以提供許多更好地處理該異常的重要信息,或者提供一些可以附加到錯誤日誌的信息,以便以後進行分析。 

  爲了解決這一問題,可以在拋出異常語句期間,在異常對象的構造函數中生成一個堆棧跟蹤。ExceptionTracer 是示範這種行爲的一個類。 

  清單 1. 在異常對象構造函數中生成一個堆棧跟蹤
  1. // Sample Program:
  2. // Compiler: gcc 3.2.3 20030502
  3. // Linux: Red Hat
  4. #include <execinfo.h>
  5. #include <signal.h>
  6. #include <exception>
  7. #include <iostream>
  8. using namespace std;
  9. /////////////////////////////////////////////
  10. class ExceptionTracer
  11. {
  12.  public:
  13.   ExceptionTracer()
  14.   {
  15.    void * array[25];
  16.    int nSize = backtrace(array, 25);
  17.    char ** symbols = backtrace_symbols(array, nSize);
  18.  
  19.    for (int i = 0; i < nSize; i++)
  20.    {
  21.     cout << symbols[i] << endl;
  22.    }
  23.   free(symbols);
  24.  }
  25. };
管理信號

  每當進程執行一個令人討厭的動作,以致於 Linux? 內核發出一個信號時,該信號都必須被處理。信號處理程序通常會釋放一些重要資源並終止應用程序。在這種情況下,堆棧上的所有對象實例都處於未破壞狀態。另一方面,如果這些信號被轉換成 C++ 異常,那麼您可以優雅地調用其構造函數,並安排多層 catch 塊,以便更好地處理這些信號。 

  清單 2 中定義的 SignalExceptionClass,提供了表示內核可能發出信號的 C++ 異常的抽象。SignalTranslator 是一個基於 SignalExceptionClass 的模板類,它通常用來實現到 C++ 異常的轉換。在任何瞬間,只能有一個信號處理程序處理一個活動進程的一個信號。因此,SignalTranslator 採用了 singleton 設計模式。整體概念通過用於 SIGSEGV 的 SegmentationFault 類和用於 SIGFPE 的 FloatingPointException 類得到了展示。 

  清單 2. 將信號轉換成異常
  1. template <class SignalExceptionClass> class SignalTranslator
  2. {
  3.  private:
  4.   class SingleTonTranslator
  5.   {
  6.    public:
  7.     SingleTonTranslator()
  8.     {
  9.      signal(SignalExceptionClass::GetSignalNumber(), SignalHandler);
  10.     }
  11.     static void SignalHandler(int)
  12.     {
  13.      throw SignalExceptionClass();
  14.     }
  15.    };
  16.  public:
  17.   SignalTranslator()
  18.   {
  19.    static SingleTonTranslator s_objTranslator;
  20.   }
  21. };
  22. // An example for SIGSEGV
  23. class SegmentationFault : public ExceptionTracer, public exception
  24. {
  25.  public:
  26.   static int GetSignalNumber() {return SIGSEGV;}
  27. };
  28. SignalTranslator<SegmentationFault> g_objSegmentationFaultTranslator;
  29. // An example for SIGFPE
  30. class FloatingPointException : public ExceptionTracer, public exception
  31. {
  32.  public:
  33.   static int GetSignalNumber() {return SIGFPE;}
  34. };
  35. SignalTranslator<FloatingPointException> g_objFloatingPointExceptionTranslator;
管理構造函數和析構函數中的異常

  在全局(靜態全局)變量的構造和析構期間,每個 ANSI C++ 都捕獲到異常是不可能的。因此,ANSI C++ 不建議在那些其實例可能被定義爲全局實例(靜態全局實例)的類的構造函數和析構函數中拋出異常。換一種說法就是永遠都不要爲那些其構造函數和析構函數可能拋出異常的類定義全局(靜態全局)實例。不過,如果假定有一個特定編譯器和一個特定系統,那麼可能可以這樣做,幸運的是,對於 Linux 上的 GCC,恰好是這種情況。 

  使用 ExceptionHandler 類可以展示這一點,該類也採用了 singleton 設計模式。其構造函數註冊了一個未捕獲的處理程序。因爲每次只能有一個未捕獲的處理程序處理一個活動進程,構造函數應該只被調用一次,因此要採用 singleton 模式。應該在定義有問題的實際全局(靜態全局)變量之前定義 ExceptionHandler 的全局(靜態全局)實例。 

  清單 3. 處理構造函數中的異常
  1. class ExceptionHandler
  2. {
  3.  private:
  4.   class SingleTonHandler
  5.   {
  6.    public:
  7.     SingleTonHandler()
  8.     {
  9.      set_terminate(Handler);
  10.     }
  11.     static void Handler()
  12.     {
  13.      // Exception from construction/destruction of global variables
  14.      try
  15.      {
  16.       // re-throw
  17.       throw;
  18.      }
  19.      catch (SegmentationFault &)
  20.      {
  21.       cout << "SegmentationFault" << endl;
  22.      }
  23.      catch (FloatingPointException &)
  24.      {
  25.       cout << "FloatingPointException" << endl;
  26.      }
  27.      catch (...)
  28.      {
  29.       cout << "Unknown Exception" << endl;
  30.      }
  31.      //if this is a thread performing some core activity
  32.      abort();
  33.      // else if this is a thread used to service requests
  34.      // pthread_exit();
  35.     }
  36.   };
  37.   public:
  38.    ExceptionHandler()
  39.    {
  40.     static SingleTonHandler s_objHandler;
  41.    }
  42.   };
  43. //////////////////////////////////////////////////////////////////////////
  44.   class A
  45.   {
  46.    public:
  47.     A()
  48.     {
  49.      //int i = 0, j = 1/i;
  50.      *(int *)0 = 0;
  51.     }
  52.   };
  53.   // Before defining any global variable, we define a dummy instance
  54.   // of ExceptionHandler object to make sure that
  55.   // ExceptionHandler::SingleTonHandler::SingleTonHandler() is invoked
  56.   ExceptionHandler g_objExceptionHandler;
  57.   A g_a;
  58.   //////////////////////////////////////////////////////////////////////////
  59.   int main(int argc, char* argv[])
  60.   {
  61.    return 0;
  62.   }
處理多線程程序中的異常

  有時一些異常沒有被捕獲,這將造成進程異常中止。不過很多時候,進程包含多個線程,其中少數線程執行核心應用程序邏輯,同時,其餘線程爲外部請求提供服務。如果服務線程因編程錯誤而沒有處理某個異常,則會造成整個應用程序崩潰。這一點可能是不受人們歡迎的,因爲它會通過嚮應用程序傳送不合法的請求而助長拒絕服務攻擊。爲了避免這一點,未捕獲處理程序可以決定是請求異常中止調用,還是請求線程退出調用。清單 3 中 ExceptionHandler::SingleTonHandler::Handler() 函數的末尾處展示了該處理程序。 

  結束語

  我簡單地討論了少許 C++ 編程設計模式,以便更好地執行以下任務: 

  ·在拋出異常的時候追蹤異常的來源。
  ·將信號從內核程序轉換成 C++ 異常。
  ·捕獲構造和/或析構全局變量期間拋出的異常。
  ·多線程進程中的異常處理。
 
  我希望您能採用這些技巧中的一些來開發無憂代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章