Making your C++ code robust
分享 2011.07.20 瀏覽次數(shù):8310次
Making your C++ code robust
- Introduction
在實(shí)際的項(xiàng)目中,當(dāng)項(xiàng)目的代碼量不斷增加的時(shí)候,你會(huì)發(fā)現(xiàn)越來(lái)越難管理和跟蹤其各個(gè)組件,如其不善,很容易就引入BUG。因此、我們應(yīng)該掌握一些能讓我們程序更加健壯的方法。
這篇文章提出了一些建議,能有引導(dǎo)我們寫(xiě)出更加強(qiáng)壯的代碼,以避免產(chǎn)生災(zāi)難性的錯(cuò)誤。即使、因?yàn)槠鋸?fù)雜性和項(xiàng)目團(tuán)隊(duì)結(jié)構(gòu),你的程序目前不遵循任何編碼規(guī)則,按照下面列出的簡(jiǎn)單的規(guī)則可以幫助您避免大多數(shù)的崩潰情況。
- Background
先來(lái)介紹下作者開(kāi)發(fā)一些軟件(CrashRpt),你可以http://code.google.com/p/crashrpt/網(wǎng)站上下載源代碼。CrashRpt 顧名思義軟件崩潰記錄軟件(庫(kù)),它能夠自動(dòng)提交你電腦上安裝的軟件錯(cuò)誤記錄。它通過(guò)以太網(wǎng)直接將這些錯(cuò)誤記錄發(fā)送給你,這樣方便你跟蹤軟件問(wèn)題,并及時(shí)修改,使得用戶(hù)感覺(jué)到每次發(fā)布的軟件都有很大的提高,這樣他們自然很高興。
圖 1、CrashRpt 庫(kù)檢測(cè)到錯(cuò)誤彈出的對(duì)話(huà)框
在分析接收的錯(cuò)誤記錄的時(shí)候,我們發(fā)現(xiàn)采用下文介紹的方法能夠避免大部分程序崩潰的錯(cuò)誤。例如、局部變量未初始化導(dǎo)致數(shù)組訪(fǎng)問(wèn)越界,指針使用前未進(jìn)行檢測(cè)(NULL)導(dǎo)致訪(fǎng)問(wèn)訪(fǎng)問(wèn)非法區(qū)域等。
我已經(jīng)總結(jié)了幾條代碼設(shè)計(jì)的方法和規(guī)則,在下文一一列出,希望能夠幫助你避免犯一些錯(cuò)誤,使得你的程序更加健壯。
- Initializing Local Variables
使用未初始化的局部變量是引起程序崩潰的一個(gè)比較普遍的原因,例如、來(lái)看下面這段程序片段:
- // 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...
上面的這段代碼存在著一個(gè)潛在的錯(cuò)誤,因?yàn)闆](méi)有一個(gè)局部變量初始化了。當(dāng)你的代碼運(yùn)行的時(shí)候,這些變量將被默認(rèn)負(fù)一些錯(cuò)誤的數(shù)值。例如bExitResult 數(shù)值將被負(fù)為-135913245 ,szBuffer 必須以“\0”結(jié)尾,結(jié)果不會(huì)。因此、局部變量初始化時(shí)非常重要的,如下正確代碼:
- // 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...
注意:有人說(shuō)變量初始化會(huì)引起程序效率降低,是的,確實(shí)如此,如果你確實(shí)非常在乎程序的執(zhí)行效率,去除局部變量初始化,你得想好其后果。
- Initializing WinAPI Structures
許多Windows API都接受或則返回一些結(jié)構(gòu)體參數(shù),結(jié)構(gòu)體如果沒(méi)有正確的初始化,也很有可能引起程序崩潰。大家可能會(huì)想起用ZeroMemory宏或者memset()函數(shù)去用0填充這個(gè)結(jié)構(gòu)體(對(duì)結(jié)構(gòu)體對(duì)應(yīng)的元素設(shè)置默認(rèn)值)。但是大部分Windows API 結(jié)構(gòu)體都必須有一個(gè)cbSIze參數(shù),這個(gè)參數(shù)必須設(shè)置為這個(gè)結(jié)構(gòu)體的大小。
看看下面代碼,如何初始化Windows API結(jié)構(gòu)體參數(shù):
- 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);
注意:千萬(wàn)不要用ZeroMemory和memset去初始化那些包括結(jié)構(gòu)體對(duì)象的結(jié)構(gòu)體,這樣很容易破壞其內(nèi)部結(jié)構(gòu)體,從而導(dǎo)致程序崩潰.
- // 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;
- 這里最好是用結(jié)構(gòu)體的構(gòu)造函數(shù)對(duì)其成員進(jìn)行初始化.
- // 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
在函數(shù)設(shè)計(jì)的時(shí)候,對(duì)傳入的參數(shù)進(jìn)行檢測(cè)是一直都推薦的。例如、如果你設(shè)計(jì)的函數(shù)是公共API的一部分,它可能被外部客戶(hù)端調(diào)用,這樣很難保證客戶(hù)端傳進(jìn)入的參數(shù)就是正確的。
例如,讓我們來(lái)看看這個(gè)hypotethical DrawVehicle() 函數(shù),它可以根據(jù)不同的質(zhì)量來(lái)繪制一輛跑車(chē),這個(gè)質(zhì)量數(shù)值(nDrawingQaulity )是0~100。prcDraw 定義這輛跑車(chē)的輪廓區(qū)域。
看看下面代碼,注意觀(guān)察我們是如何在使用函數(shù)參數(shù)之前進(jìn)行參數(shù)檢測(cè):
- 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
在指針使用之前,不檢測(cè)是非常普遍的,這個(gè)可以說(shuō)是我們引起軟件崩潰最有可能的原因。如果你用一個(gè)指針,這個(gè)指針剛好是NULL,那么你的程序在運(yùn)行時(shí),將報(bào)出異常。
- CVehicle* pVehicle = GetCurrentVehicle();
- // Validate pointer
- if(pVehicle==NULL)
- {
- // Invalid pointer, do not use it!
- return FALSE;
- }
- Initializing Function Output
如果你的函數(shù)創(chuàng)建了一個(gè)對(duì)象,并要將它作為函數(shù)的返回參數(shù)。那么記得在使用之前把他復(fù)制為NULL。如不然,這個(gè)函數(shù)的調(diào)用者將使用這個(gè)無(wú)效的指針,進(jìn)而一起程序錯(cuò)誤。如下錯(cuò)誤代碼:
- 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
在內(nèi)存釋放之后,無(wú)比將指針復(fù)制為NULL。這樣可以確保程序的沒(méi)有那個(gè)地方會(huì)再使用無(wú)效指針。其實(shí)就是,訪(fǎng)問(wèn)一個(gè)已經(jīng)被刪除的對(duì)象地址,將引起程序異常。如下代碼展示如何清除一個(gè)指針指向的對(duì)象:
- // Create object
- CVehicle* pVehicle = new CVehicle();
- delete pVehicle; // Free pointer
- pVehicle = NULL; // Set pointer with NULL
- Cleaning Up Released Handles
在釋放一個(gè)句柄之前,務(wù)必將這個(gè)句柄復(fù)制偽NULL (0或則其他默認(rèn)值)。這樣能夠保證程序其他地方不會(huì)重復(fù)使用無(wú)效句柄??纯慈缦麓a,如何清除一個(gè)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
如果你分配一個(gè)單獨(dú)的對(duì)象,可以直接使用new ,同樣你釋放單個(gè)對(duì)象的時(shí)候,可以直接使用delete . 然而,申請(qǐng)一個(gè)對(duì)象數(shù)組對(duì)象的時(shí)候可以使用new,但是釋放的時(shí)候就不能使用delete ,而必須使用delete[]:
- // Create an array of objects
- CVehicle* paVehicles = new CVehicle[10];
- delete [] paVehicles; // Free pointer to array
- paVehicles = NULL; // Set pointer with NULL
- or
- // 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
有時(shí)候,程序需要?jiǎng)討B(tài)分配一段緩沖區(qū),這個(gè)緩沖區(qū)是在程序運(yùn)行的時(shí)候決定的。例如、你需要讀取一個(gè)文件的內(nèi)容,那么你就需要申請(qǐng)?jiān)撐募笮〉木彌_區(qū)來(lái)保存該文件的內(nèi)容。在申請(qǐng)這段內(nèi)存之前,請(qǐng)注意,malloc() or new是不能申請(qǐng)0字節(jié)的內(nèi)存,如不然,將導(dǎo)致malloc() or new函數(shù)調(diào)用失敗。傳遞錯(cuò)誤的參數(shù)給malloc() 函數(shù)將導(dǎo)致C運(yùn)行時(shí)錯(cuò)誤。如下代碼展示如何動(dòng)態(tài)申請(qǐng)內(nèi)存:
- // 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];
為了進(jìn)一步了解如何正確的分配內(nèi)存,你可以讀下Secure Coding Best Practices for Memory Allocation in C and C++這篇文章。
- Using Asserts Carefully
Asserts用語(yǔ)調(diào)試模式檢測(cè)先決條件和后置條件。但當(dāng)我們編譯器處于release模式的時(shí)候,Asserts在預(yù)編階段被移除。因此,用Asserts是不能夠檢測(cè)我們的程序狀態(tài),錯(cuò)誤代碼如下:
- #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模式下檢測(cè)我們的程序,在release 模式下卻不能。所以我們還是不得不用if()來(lái)這步檢測(cè)操作。正確的代碼如下;
- 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
斷定一個(gè)函數(shù)執(zhí)行一定成功是一種常見(jiàn)的錯(cuò)誤。當(dāng)你調(diào)用一個(gè)函數(shù)的時(shí)候,建議檢查下返回代碼和返回參數(shù)的值。如下代碼持續(xù)調(diào)用Windows API ,程序是否繼續(xù)執(zhí)行下去依賴(lài)于該函數(shù)的返回結(jié)果和返回參數(shù)值。
- 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, &n, bsp;
- 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
如果你經(jīng)常使用用享對(duì)象指針,如COM 接口等,那么建議使用智能指針來(lái)處理。智能指針會(huì)自動(dòng)幫助你維護(hù)對(duì)象引用記數(shù),并且保證你不會(huì)訪(fǎng)問(wèn)到被刪除的對(duì)象。這樣,不需要關(guān)心和控制接口的生命周期。關(guān)于智能指針的進(jìn)一步知識(shí)可以看看Smart Pointers - What, Why, Which? 和 Implementing a Simple Smart Pointer in C++這兩篇文章。
如面是一個(gè)展示使用ATL's CComPtr template 智能指針的代碼,該部分代碼來(lái)至于MSDN。
洞悉市場(chǎng)趨勢(shì)演變讓傳播回歸社會(huì)
免費(fèi)獲取網(wǎng)站建設(shè)與網(wǎng)絡(luò)推廣方案報(bào)價(jià)
-
關(guān)于我們
杭州帷拓科技有限公司,是一家新型的全案網(wǎng)絡(luò)開(kāi)發(fā)公司,作為以互聯(lián)網(wǎng)高端網(wǎng)站建設(shè)、APP開(kāi)發(fā)、小程序開(kāi)發(fā)為核心的專(zhuān)業(yè)網(wǎng)絡(luò)技術(shù)服務(wù)供應(yīng)商,帷拓科技致力于全面分析市場(chǎng)環(huán)境、衡量與預(yù)測(cè)市場(chǎng)需求、整合區(qū)別于行業(yè)競(jìng)爭(zhēng)對(duì)手的絕對(duì)優(yōu)勢(shì),結(jié)合品牌理念深度挖掘項(xiàng)目?jī)?yōu)勢(shì)和產(chǎn)品價(jià)值,提升客戶(hù)品牌認(rèn)知、認(rèn)可度。
-
我們的客戶(hù)
帷拓科技?xì)v經(jīng)十年沉淀,與國(guó)內(nèi)外上千家客戶(hù)達(dá)成合作關(guān)系,其中穩(wěn)定合作的公司有:浙江華為、浙江移動(dòng)、浙江5G產(chǎn)業(yè)聯(lián)盟、浙江省社科院、綠城足球俱樂(lè)部、娃哈哈雙語(yǔ)學(xué)校、健康中國(guó)杭州峰會(huì)、科雷機(jī)電等,帷拓科技始終堅(jiān)持“帷有專(zhuān)業(yè),才能拓展無(wú)限”的服務(wù)理念,堅(jiān)持“認(rèn)真堅(jiān)持細(xì)節(jié)”的優(yōu)質(zhì)服務(wù)理念,不斷完善自身,成就企業(yè),最終實(shí)現(xiàn)共贏(yíng)。
-
我們的業(yè)務(wù)
帷拓科技主營(yíng)業(yè)務(wù)范圍包含互聯(lián)網(wǎng)高端網(wǎng)站建設(shè)、APP開(kāi)發(fā)、小程序開(kāi)發(fā)、商城網(wǎng)站建設(shè)、公眾號(hào)運(yùn)營(yíng)以及數(shù)字營(yíng)銷(xiāo)等,涵蓋了服務(wù)、房產(chǎn)、數(shù)碼、服裝、物流貿(mào)易等行業(yè),根據(jù)品牌現(xiàn)狀,為每個(gè)客戶(hù)量身定制項(xiàng)目整體服務(wù)方案,以敏銳的市場(chǎng)洞察力、創(chuàng)新的市場(chǎng)策劃能力,全面把握市場(chǎng)變化,為客戶(hù)實(shí)現(xiàn)從企業(yè)到消費(fèi)者的價(jià)值轉(zhuǎn)換。
立即與帷拓項(xiàng)目顧問(wèn)通話(huà)
86-571-88023217
您也可以咨詢(xún)我們的預(yù)約資深顧問(wèn)(微信號(hào):182-5841-0071)
信息保護(hù)中請(qǐng)放心填寫(xiě)