讓你的代碼變的更加強大(Making your C++ code robust)

來源:互聯網
上載者:User
 Making your C++ code robust
  • Introduction

       在實際的項目中,當項目的代碼量不斷增加的時候,你會發現越來越難管理和跟蹤其各個組件,如其不善,很容易就引入BUG。因此、我們應該掌握一些能讓我們程式更加健壯的方法。

       這篇文章提出了一些建議,能有引導我們寫出更加強壯的代碼,以避免產生災難性的錯誤。即使、因為其複雜性和項目團隊結構,你的程式目前不遵循任何編碼規則,按照下面列出的簡單的規則可以協助您避免大多數的崩潰情況。

  • Background

        先來介紹下作者開發一些軟體(CrashRpt),你可以http://code.google.com/p/crashrpt/網站上下載原始碼。CrashRpt 顧名思義軟體崩潰記錄軟體(庫),它能夠自動認可你電腦上安裝的軟體錯誤記錄。它通過乙太網路直接將這些錯誤記錄發送給你,這樣方便你跟蹤軟體問題,並及時修改,使得使用者感覺到每次發布的軟體都有很大的提高,這樣他們自然很高興。

圖 1、CrashRpt 庫檢測到錯誤彈出的對話方塊

       在分析接收的錯誤記錄的時候,我們發現採用下文介紹的方法能夠避免大部分程式崩潰的錯誤。例如、局部變數未初始化導致數組訪問越界,指標使用前未進行檢測(NULL)導致訪問訪問非法地區等。

      我已經總結了幾條代碼設計的方法和規則,在下文一一列出,希望能夠協助你避免犯一些錯誤,使得你的程式更加健壯。

  • Initializing Local Variables 

     使用未初始化的局部變數是引起程式崩潰的一個比較普遍的原因,例如、來看下面這段程式片段:

  // Define local variables  BOOL bExitResult; // This will be TRUE if the function exits successfully  FILE* f; // Handle to file  TCHAR szBuffer[_MAX_PATH];   // String buffer      // Do something with variables above... 

     上面的這段代碼存在著一個潛在的錯誤,因為沒有一個局部變數初始化了。當你的代碼啟動並執行時候,這些變數將被預設負一些錯誤的數值。例如bExitResult 數值將被負為-135913245 ,szBuffer 必須以“\0”結尾,結果不會。因此、局部變數初始化時非常重要的,如下正確代碼:

  // Define local variables    // Initialize function exit code with FALSE to indicate failure assumption  BOOL bExitResult = FALSE; // This will be TRUE if the function exits successfully  // Initialize file handle with NULL  FILE* f = NULL; // Handle to file  // Initialize string buffer with empty string  TCHAR szBuffer[_MAX_PATH] = _T("");   // String buffer  // Do something with variables above... 

    注意:有人說變數初始化會引起程式效率降低,是的,確實如此,如果你確實非常在乎程式的執行效率,去除局部變數初始化,你得想好其後果。

  • Initializing WinAPI Structures

       許多Windows API都接受或則返回一些結構體參數,結構體如果沒有正確的初始化,也很有可能引起程式崩潰。大家可能會想起用ZeroMemory宏或者memset()函數去用0填充這個結構體(對結構體對應的元素設定預設值)。但是大部分Windows API 結構體都必須有一個cbSIze參數,這個參數必須設定為這個結構體的大小。

       看看下面代碼,如何初始化Windows API結構體參數:

  NOTIFYICONDATA nf; // WinAPI structure  memset(&nf,0,sizeof(NOTIFYICONDATA)); // Zero memory  nf.cbSize = sizeof(NOTIFYICONDATA); // Set structure size!  // Initialize other structure members  nf.hWnd = hWndParent;  nf.uID = 0;     nf.uFlags = NIF_ICON | NIF_TIP;  nf.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);  _tcscpy_s(nf.szTip, 128, _T("Popup Tip Text"));          // Add a tray icon  Shell_NotifyIcon(NIM_ADD, &nf);

      注意:千萬不要用ZeroMemory和memset去初始化那些包括結構體對象的結構體,這樣很容易破壞其內部結構體,從而導致程式崩潰.

  // Declare a C++ structure  struct ItemInfo  {    std::string sItemName; // The structure has std::string object inside    int nItemValue;  };    // Init the structure  ItemInfo item;  // Do not use memset()! It can corrupt the structure  // memset(&item, 0, sizeof(ItemInfo));  // Instead use the following  item.sItemName = "item1";  item.nItemValue = 0;      這裡最好是用結構體的建構函式對其成員進行初始化.  // Declare a C++ structure  struct ItemInfo  {    // Use structure constructor to set members with default values    ItemInfo()    {      sItemName = _T("unknown");      nItemValue = -1;    }            std::string sItemName; // The structure has std::string object inside    int nItemValue;  };  // Init the structure  ItemInfo item;  // Do not use memset()! It can corrupt the structure  // memset(&item, 0, sizeof(ItemInfo));  // Instead use the following  item.sItemName = "item1";  item.nItemValue = 0;    
  • Validating Function Input 

      在函數設計的時候,對傳入的參數進行檢測是一直都推薦的。例如、如果你設計的函數是公用API的一部分,它可能被外部客戶端調用,這樣很難保證用戶端傳進入的參數就是正確的。

      例如,讓我們來看看這個hypotethical DrawVehicle() 函數,它可以根據不同的品質來繪製一輛跑車,這個品質數值(nDrawingQaulity )是0~100。prcDraw 定義這輛跑車的輪廓地區。

      看看下面代碼,注意觀察我們是如何在使用函數參數之前進行參數檢測:

BOOL DrawVehicle(HWND hWnd, LPRECT prcDraw, int nDrawingQuality)  {    // Check that window is valid    if(!IsWindow(hWnd))      return FALSE;     // Check that drawing rect is valid    if(prcDraw==NULL)      return FALSE;     // Check drawing quality is valid    if(nDrawingQuality<0 || nDrawingQuality>100)      return FALSE;       // Now it's safe to draw the vehicle     // ...     return TRUE;  }
  • Validating Pointers

       在指標使用之前,不檢測是非常普遍的,這個可以說是我們引起軟體崩潰最有可能的原因。如果你用一個指標,這個指標剛好是NULL,那麼你的程式在運行時,將報出異常。

  CVehicle* pVehicle = GetCurrentVehicle();    // Validate pointer  if(pVehicle==NULL)  {    // Invalid pointer, do not use it!    return FALSE;  }
  • Initializing Function Output

     如果你的函數建立了一個對象,並要將它作為函數的返回參數。那麼記得在使用之前把他複製為NULL。如不然,這個函數的調用者將使用這個無效的指標,進而一起程式錯誤。如下錯誤碼:

int CreateVehicle(CVehicle** ppVehicle)  {    if(CanCreateVehicle())    {      *ppVehicle = new CVehicle();      return 1;    }         // If CanCreateVehicle() returns FALSE,    // the pointer to *ppVehcile would never be set!    return 0;  }      正確的代碼如下;  int CreateVehicle(CVehicle** ppVehicle)  {    // First initialize the output parameter with NULL    *ppVehicle = NULL;     if(CanCreateVehicle())    {      *ppVehicle = new CVehicle();      return 1;    }         return 0;  }
  • Cleaning Up Pointers to Deleted Objects

     在記憶體釋放之後,無比將指標複製為NULL。這樣可以確保程式的沒有那個地方會再使用無效指標。其實就是,訪問一個已經被刪除的對象地址,將引起程式異常。如下代碼展示如何清除一個指標指向的對象:

 // Create object CVehicle* pVehicle = new CVehicle(); delete pVehicle; // Free pointer pVehicle = NULL; // Set pointer with NULL
  • Cleaning Up Released Handles 

      在釋放一個控制代碼之前,務必將這個控制代碼複製偽NULL (0或則其他預設值)。這樣能夠保證程式其他地方不會重複使用無效控制代碼。看看如下代碼,如何清除一個Windows API的檔案控制代碼:

  HANDLE hFile = INVALID_HANDLE_VALUE;     // Open file  hFile = CreateFile(_T("example.dat"), FILE_READ|FILE_WRITE, FILE_OPEN_EXISTING);  if(hFile==INVALID_HANDLE_VALUE)  {    return FALSE; // Error opening file  }   // Do something with file   // Finally, close the handle  if(hFile!=INVALID_HANDLE_VALUE)  {    CloseHandle(hFile);   // Close handle to file    hFile = INVALID_HANDLE_VALUE;   // Clean up handle  } 

     下面代碼展示如何清除File *控制代碼:

  // First init file handle pointer with NULL  FILE* f = NULL;   // Open handle to file  errno_t err = _tfopen_s(_T("example.dat"), _T("rb"));  if(err!=0 || f==NULL)    return FALSE; // Error opening file   // Do something with file   // When finished, close the handle  if(f!=NULL) // Check that handle is valid  {    fclose(f);    f = NULL; // Clean up pointer to handle  } 
  • Using delete [] Operator for Arrays 

     如果你分配一個單獨的對象,可以直接使用new ,同樣你釋放單個對象的時候,可以直接使用delete . 然而,申請一個對象數組對象的時候可以使用new,但是釋放的時候就不能使用delete ,而必須使用delete[]:

 // Create an array of objects CVehicle* paVehicles = new CVehicle[10]; delete [] paVehicles; // Free pointer to array paVehicles = NULL; // Set pointer with NULLor // Create a buffer of bytes LPBYTE pBuffer = new BYTE[255]; delete [] pBuffer; // Free pointer to array pBuffer = NULL; // Set pointer with NULL
  • Allocating Memory Carefully 

     有時候,程式需要動態分配一段緩衝區,這個緩衝區是在程式啟動並執行時候決定的。例如、你需要讀取一個檔案的內容,那麼你就需要申請該檔案大小的緩衝區來儲存該檔案的內容。在申請這段記憶體之前,請注意,malloc() or new是不能申請0位元組的記憶體,如不然,將導致malloc() or new函數調用失敗。傳遞錯誤的參數給malloc() 函數將導致C執行階段錯誤。如下代碼展示如何動態申請記憶體:

  // Determine what buffer to allocate.  UINT uBufferSize = GetBufferSize();    LPBYTE* pBuffer = NULL; // Init pointer to buffer   // Allocate a buffer only if buffer size > 0  if(uBufferSize>0)   pBuffer = new BYTE[uBufferSize];

      為了進一步瞭解如何正確的分配記憶體,你可以讀下Secure Coding Best Practices for Memory Allocation in C and C++這篇文章。

  • Using Asserts Carefully

       Asserts用語偵錯模式檢測先決條件和後置條件。但當我們編譯器處於release模式的時候,Asserts在預編階段被移除。因此,用Asserts是不能夠檢測我們的程式狀態,錯誤碼如下:

 #include <assert.h>    // This function reads a sports car's model from a file  CVehicle* ReadVehicleModelFromFile(LPCTSTR szFileName)  {    CVehicle* pVehicle = NULL; // Pointer to vehicle object     // Check preconditions    assert(szFileName!=NULL); // This will be removed by preprocessor in Release mode!    assert(_tcslen(szFileName)!=0); // This will be removed in Release mode!     // Open the file    FILE* f = _tfopen(szFileName, _T("rt"));     // Create new CVehicle object    pVehicle = new CVehicle();     // Read vehicle model from file     // Check postcondition     assert(pVehicle->GetWheelCount()==4); // This will be removed in Release mode!     // Return pointer to the vehicle object    return pVehicle;  }

      看看上述的代碼,Asserts能夠在debug模式下檢測我們的程式,在release 模式下卻不能。所以我們還是不得不用if()來這步檢測操作。正確的代碼如下;

 CVehicle* ReadVehicleModelFromFile(LPCTSTR szFileName, )  {    CVehicle* pVehicle = NULL; // Pointer to vehicle object     // Check preconditions    assert(szFileName!=NULL); // This will be removed by preprocessor in Release mode!    assert(_tcslen(szFileName)!=0); // This will be removed in Release mode!     if(szFileName==NULL || _tcslen(szFileName)==0)      return NULL; // Invalid input parameter     // Open the file    FILE* f = _tfopen(szFileName, _T("rt"));     // Create new CVehicle object    pVehicle = new CVehicle();     // Read vehicle model from file     // Check postcondition     assert(pVehicle->GetWheelCount()==4); // This will be removed in Release mode!     if(pVehicle->GetWheelCount()!=4)    {       // Oops... an invalid wheel count was encountered!        delete pVehicle;       pVehicle = NULL;    }     // Return pointer to the vehicle object    return pVehicle;  }
  • Checking Return Code of a Function 

        斷定一個函數執行一定成功是一種常見的錯誤。當你調用一個函數的時候,建議檢查下傳回碼和返回參數的值。如下代碼持續調用Windows API ,程式是否繼續執行下去依賴於該函數的返回結果和返回參數值。

HRESULT hres = E_FAIL;    IWbemServices *pSvc = NULL;    IWbemLocator *pLoc = NULL;        hres =  CoInitializeSecurity(        NULL,         -1,                          // COM authentication        NULL,                        // Authentication services        NULL,                        // Reserved        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication         RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation          NULL,                        // Authentication info        EOAC_NONE,                   // Additional capabilities         NULL                         // Reserved        );                          if (FAILED(hres))    {        // Failed to initialize security        if(hres!=RPC_E_TOO_LATE)            return FALSE;    }        hres = CoCreateInstance(        CLSID_WbemLocator,                     0,         CLSCTX_INPROC_SERVER,         IID_IWbemLocator, (LPVOID *) &pLoc);    if (FAILED(hres) || !pLoc)    {        // Failed to create IWbemLocator object.         return FALSE;                   }       hres = pLoc->ConnectServer(         _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace         NULL,                    // User name. NULL = current user         NULL,                    // User password. NULL = current         0,                       // Locale. NULL indicates current         NULL,                    // Security flags.         0,                       // Authority (e.g. Kerberos)         0,                       // Context object          &pSvc                    // pointer to IWbemServices proxy         );        if (FAILED(hres) || !pSvc)    {        // Couldn't conect server        if(pLoc) pLoc->Release();             return FALSE;      }    hres = CoSetProxyBlanket(       pSvc,                        // Indicates the proxy to set       RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx       RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx       NULL,                        // Server principal name        RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx       NULL,                        // client identity       EOAC_NONE                    // proxy capabilities     );    if (FAILED(hres))    {        // Could not set proxy blanket.        if(pSvc) pSvc->Release();        if(pLoc) pLoc->Release();             return FALSE;                   } 
  • Using Smart Pointers

       如果你經常使用用享對象指標,如COM 介面等,那麼建議使用智能指標來處理。智能指標會自動協助你維護對象引用記數,並且保證你不會訪問到被刪除的對象。這樣,不需要關心和控制介面的生命週期。關於智能指標的進一步知識可以看看Smart Pointers - What, Why, Which? 和 Implementing a Simple Smart Pointer in C++這兩篇文章。

       如面是一個展示使用ATL's CComPtr template 智能指標的代碼,該部分代碼來至於MSDN。

#include <windows.h>#include <shobjidl.h> #include <atlbase.h> // Contains the declaration of CComPtr.int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow){    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |         COINIT_DISABLE_OLE1DDE);    if (SUCCEEDED(hr))    {        CComPtr<IFileOpenDialog> pFileOpen;        // Create the FileOpenDialog object.        hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));        if (SUCCEEDED(hr))        {            // Show the Open dialog box.            hr = pFileOpen->Show(NULL);            // Get the file name from the dialog box.            if (SUCCEEDED(hr))            {                CComPtr<IShellItem> pItem;                hr = pFileOpen->GetResult(&pItem);                if (SUCCEEDED(hr))                {                    PWSTR pszFilePath;                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);                    // Display the file name to the user.                    if (SUCCEEDED(hr))                    {                        MessageBox(NULL, pszFilePath, L"File Path", MB_OK);                        CoTaskMemFree(pszFilePath);                    }                }                // pItem goes out of scope.            }            // pFileOpen goes out of scope.        }        CoUninitialize();    }    return 0;}  
  • Using == Operator Carefully

       先來看看如下代碼;

  CVehicle* pVehicle = GetCurrentVehicle();     // Validate pointer  if(pVehicle==NULL) // Using == operator to compare pointer with NULL     return FALSE;    // Do something with the pointer  pVehicle->Run();

      上面的代碼是正確的,用語指標檢測。但是如果不小心用“=”替換了“==”,如下代碼;

 CVehicle* pVehicle = GetCurrentVehicle();   // Validate pointer  if(pVehicle=NULL) // Oops! A mistyping here!     return FALSE;    // Do something with the pointer  pVehicle->Run(); // Crash!!! 

        看看上面的代碼,這個的一個失誤將導致程式崩潰。

       這樣的錯誤是可以避免的,只需要將等號左右兩邊交換一下就可以了。如果在修改代碼的時候,你不小心產生這種失誤,這個錯誤在程式編譯的時候將被檢測出來。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.