CObject::AssertValid 成員函數(shù)提供對(duì)對(duì)象內(nèi)部狀態(tài)的運(yùn)行時(shí)檢查,。盡管從CObject派生類時(shí)不需要重寫 AssertValid,,但可以通過重寫使您的類更安全可靠,。AssertValid應(yīng)在對(duì)象的所有成員變量上執(zhí)行斷言,以驗(yàn)證它們包含有效值,。例如,,它應(yīng)檢查指針成員變量不為 NULL。
下面的示例顯示如何聲明 AssertValid 函數(shù): class CPerson : public CObject { protected: CString m_strName; float m_salary; public: #ifdef _DEBUG virtual void AssertValid() const; // Override #endif // ... }; 當(dāng)重寫 AssertValid 時(shí),,在執(zhí)行您自己的檢查之前請(qǐng)調(diào)用 AssertValid 的基類版本,。然后使用 ASSERT 宏檢查您的派生類特有的成員,,如下所示: #ifdef _DEBUG void CPerson::AssertValid() const { // call inherited AssertValid first CObject::AssertValid(); // check CPerson members... ASSERT( !m_strName.IsEmpty()); // Must have a name ASSERT( m_salary > 0 ); // Must have an income } #endif 如果任何成員變量存儲(chǔ)對(duì)象,則可以使用 ASSERT_VALID 宏測(cè)試它們的內(nèi)部有效性(如果它們的類重寫了 AssertValid),。 例如,,考慮 CMyData 類,該類在其成員變量之一中存儲(chǔ)了一個(gè) CObList,。CObList 變量 m_DataList 存儲(chǔ)了一個(gè) CPerson 對(duì)象的集合,。CMyData 的簡(jiǎn)化聲明如下所示: class CMyData : public CObject { // Constructor and other members ... protected: CObList* m_pDataList; // Other declarations ... public: #ifdef _DEBUG virtual void AssertValid( ) const; // Override #endif // Etc. ... }; CMyData 中重寫的 AssertValid 如下所示: #ifdef _DEBUG void CMyData::AssertValid( ) const { // Call inherited AssertValid CObject::AssertValid( ); // Check validity of CMyData members ASSERT_VALID( m_pDataList ); // ... } #endif CMyData 使用 AssertValid 機(jī)制測(cè)試其數(shù)據(jù)成員中存儲(chǔ)的對(duì)象的有效性。CMyData 中重寫的 AssertValid 為它自己的 m_pDataList 成員變量調(diào)用 ASSERT_VALID 宏,。 因?yàn)?nbsp; CObList 類也重寫 AssertValid,,所以有效性測(cè)試不在該級(jí)別停止。該重寫對(duì)列表的內(nèi)部狀態(tài)執(zhí)行附加有效性測(cè)試,。因此,,對(duì) CMyData 對(duì)象的有效性測(cè)試將導(dǎo)致對(duì)存儲(chǔ)的 CObList 列表對(duì)象內(nèi)部狀態(tài)的附加有效性測(cè)試。 再多進(jìn)行一些操作,,還可以添加對(duì)存儲(chǔ)在列表中的 CPerson 對(duì)象的有效性測(cè)試,。可以從 CObList 派生 CPersonList 類,,并重寫 AssertValid,。在重寫中可調(diào)用 CObject::AssertValid,然后循環(huán)訪問列表,,在列表中存儲(chǔ)的每個(gè) CPerson 對(duì)象上調(diào)用 AssertValid,。本主題開始所示的 CPerson 類已重寫了 AssertValid。 當(dāng)為調(diào)試生成時(shí),,這是一種功能極強(qiáng)的機(jī)制,。當(dāng)接著為發(fā)布生成時(shí),該機(jī)制自動(dòng)關(guān)閉,。 AssertValid 的限制 給定類的 AssertValid 函數(shù)的用戶應(yīng)注意該函數(shù)的限制,。觸發(fā)的斷言指示對(duì)象一定有誤,并且執(zhí)行將暫停,。但是,,缺少斷言只指示未找到任何問題,并不保證對(duì)象是好的,。 Dump
當(dāng)從 CObject 派生類時(shí),,在使用 DumpAllObjectsSince 將對(duì)象轉(zhuǎn)儲(chǔ)到“輸出”窗口時(shí),可以重寫 Dump 成員函數(shù)以提供附加信息,。
Dump 函數(shù)將對(duì)象的成員變量的文本化表示形式寫入轉(zhuǎn)儲(chǔ)上下文 (CDumpContext),。轉(zhuǎn)儲(chǔ)上下文類似于 I/O 流。可以使用插入運(yùn)算符 (<<) 向 CDumpContext 發(fā)送數(shù)據(jù),。 重寫 Dump 函數(shù)時(shí),,應(yīng)先調(diào)用 Dump 的基類版本以轉(zhuǎn)儲(chǔ)基類對(duì)象的內(nèi)容。然后為派生類的每個(gè)成員變量輸出文本化說明和值,。 Dump 函數(shù)的聲明如下所示: class CPerson : public CObject { public: #ifdef _DEBUG virtual void Dump( CDumpContext& dc ) const; #endif CString m_firstName; CString m_lastName; // And so on... }; 由于對(duì)象轉(zhuǎn)儲(chǔ)只在調(diào)試程序時(shí)有意義,,所以 Dump 函數(shù)的聲明用 #ifdef _DEBUG / #endif 塊括起來。 在下面的示例中,,Dump 函數(shù)先為其基類調(diào)用 Dump 函數(shù),。然后,它將每個(gè)成員變量的簡(jiǎn)短說明與該成員的值一起寫入診斷流,。 #ifdef _DEBUG void CPerson::Dump( CDumpContext& dc ) const { // Call the base class function first. CObject::Dump( dc ); // Now do the stuff for our specific class. dc << "last name: " << m_lastName << "\n" << "first name: " << m_firstName << "\n"; } #endif 必須提供 CDumpContext 參數(shù)以指定轉(zhuǎn)儲(chǔ)輸出的目的地。MFC 的“Debug”版本提供名為 afxDump 的預(yù)定義 CDumpContext 對(duì)象,,它將輸出發(fā)送到調(diào)試器,。 CPerson* pMyPerson = new CPerson; // Set some fields of the CPerson object. //... // Now dump the contents. #ifdef _DEBUG pMyPerson->Dump( afxDump ); #endif 在 MFC 程序中,可以使用 DumpAllObjectsSince 轉(zhuǎn)儲(chǔ)有關(guān)堆中尚未釋放的所有對(duì)象的說明,。DumpAllObjectsSince 轉(zhuǎn)儲(chǔ)自上個(gè) CMemoryState::Checkpoint 以來分配的所有對(duì)象,。如果未發(fā)生 Checkpoint 調(diào)用,則 DumpAllObjectsSince 將轉(zhuǎn)儲(chǔ)當(dāng)前在內(nèi)存中的所有對(duì)象和非對(duì)象,。 注意 必須先啟用診斷跟蹤,,然后才能使用 MFC 對(duì)象轉(zhuǎn)儲(chǔ)。 注意 程序退出時(shí) MFC 將自動(dòng)轉(zhuǎn)儲(chǔ)所有泄漏的對(duì)象,,因此不必創(chuàng)建代碼在該點(diǎn)轉(zhuǎn)儲(chǔ)對(duì)象,。 以下代碼通過比較兩個(gè)內(nèi)存狀態(tài)來測(cè)試內(nèi)存泄漏,并在檢測(cè)到泄漏時(shí)轉(zhuǎn)儲(chǔ)所有對(duì)象: if( diffMemState.Difference( oldMemState, newMemState ) ) { TRACE( "Memory leaked!\n" ); diffMemState.DumpAllObjectsSince(); } 轉(zhuǎn)儲(chǔ)的內(nèi)容如下所示: Dumping objects -> {5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long {4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long {3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long {2} a CPerson at $51A4 Last Name: Smith First Name: Alan Phone #: 581-0215 {1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long 大多數(shù)行開始處的大括號(hào)中的數(shù)字指定對(duì)象的分配順序,。最近分配的對(duì)象具有最高編號(hào),,并顯示在轉(zhuǎn)儲(chǔ)的頂部。 靜態(tài)庫和動(dòng)態(tài)庫的區(qū)別:
首先糾正所謂“靜態(tài)連接就是把需要的庫函數(shù)放進(jìn)你的exe之中”的說法,。在真實(shí)世界中,,有三個(gè)概念:Use static libary, static linked DLL,dynamic linked DLL. 多數(shù)人混淆了static libary和static linked DLL的概念,當(dāng)然他們有似是而非的“相似之處”,,比如都用到.lib,。
使用靜態(tài)庫(Use static libary)是把.lib和其他.obj一起build在目標(biāo)文件中, 目標(biāo)文件可以是.exe,也可以是.dll或.oxc等,。
一般情況下,,可以根本就沒有“對(duì)應(yīng)的”.dll 文件,如C Run Time(CRT)庫,。一個(gè)例子就是,,寫一個(gè)main(){},build出來并不是只有幾個(gè)字節(jié),,當(dāng)然有人會(huì)說那還有exe文件頭呢,?是,,即使加上文件 頭的尺寸,build出的執(zhí)行文件仍然“莫名的大”,。實(shí)際上那多出來的部分就是CRT靜態(tài)庫,。姑且可以把靜態(tài)庫.lib理解成外部程序的obj文件比較合 理,它包含了函數(shù)的實(shí)現(xiàn),。
下面再談static linked DLL和dynamic linked DLL又如何,?
靜態(tài)鏈接(static linked DLL)從操作上在VC的Project|Settings...|Link (tab)|General (category)|Object/library modules 中設(shè)置和添加。比如要使用SDK中的PropertySheet() API, 就要在這里添加 comctl32.lib,,然后再調(diào)用的源程序中#include <prsht.h>, 使用的地方直接調(diào)用PropertySheet(),。當(dāng)程序.exe啟動(dòng)時(shí),系統(tǒng)會(huì)把對(duì)應(yīng)comctl32.dll加載進(jìn)來,。作為DLL的靜態(tài)引入庫 的.lib不包含函數(shù)的實(shí)現(xiàn),,只包含用于系統(tǒng)加載的信息,如對(duì)應(yīng)的DLL名稱,,函數(shù)歧視地只在對(duì)應(yīng)的DLL中的便宜等等,。相比動(dòng)態(tài)鏈接而言,靜態(tài)鏈接是很 簡(jiǎn)單的,。
動(dòng)態(tài)鏈接是使用LoadLibrary()/GetProcessAddress()和FreeLibrary().
有人會(huì)想,,動(dòng)態(tài)鏈接這樣麻煩,為什么還要用呢,?這里有一個(gè)技術(shù)問題,,對(duì)這個(gè)問題的解決直接導(dǎo)致了動(dòng)態(tài)加載的需求。問題是有些DLL只在某個(gè)Windows 版本中存在,,或某個(gè)API只在某些Windows版本中被加入指定的DLL,。當(dāng)你使用靜態(tài)鏈接的.exe試圖在不支持的Windows版本上運(yùn)行時(shí),系統(tǒng) 會(huì)彈出系統(tǒng)對(duì)話框提示某某.dll無法加載或無法定位某某API的消息,,然后就中止.exe的運(yùn)行,。像這樣因?yàn)閭€(gè)別功能的實(shí)現(xiàn)依賴于某個(gè)DLL,當(dāng)這個(gè) DLL不可用時(shí)導(dǎo)致整個(gè).exe無法運(yùn)行是不明智的,。避免這樣的結(jié)局只有用動(dòng)態(tài)鏈接,。
#ifdef _DEBUG // 判斷是否定義_DEBUG
#undef THIS_FILE // 取消THIS_FILE的定義 static char THIS_FILE[]=__FILE__; // 定義THIS_FILE指向文件名 #define new DEBUG_NEW // 定義調(diào)試new宏,取代new關(guān)鍵字 #endif // 結(jié)束 如果定義了_DEBUG,,表示在調(diào)試狀態(tài)下編譯,,因此相應(yīng)修改了兩個(gè)符號(hào)的定義 THIS_FILE是一個(gè)char數(shù)組全局變量,字符串值為當(dāng)前文件的全路徑,,這樣在Debug版本中當(dāng)程序出錯(cuò)時(shí)出錯(cuò)處理代碼可用這個(gè)變量告訴你是哪個(gè)文件中的代碼有問題,。 定義 _DEBUG后,由于定義了_DEBUG,編譯器確定這是一個(gè)調(diào)試,編譯#ifdef _DEBUG和#endif之間的代碼,。#undef 表示清除當(dāng)前定義的宏,,使得THIS_FILE無定義。__FILE__ 是編譯器能識(shí)別的事先定義的ANSI C 的6個(gè)宏之一,。#define new DEBUG_NEW DEBUG_NEW定位內(nèi)存泄露并且跟蹤文件名. 在用vc時(shí),,利用AppWizard會(huì)產(chǎn)生如下代碼: #ifdef _DEBUG
#define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif 對(duì)于
#define new DEBUG_NEW
首先看msdn的解釋: Assists in finding memory leaks. You can use DEBUG_NEW everywhere in your program that you would ordinarily use the new operator to allocate heap storage.
In debug mode (when the _DEBUG symbol is defined), DEBUG_NEW keeps track of the filename and line number for each object that it allocates. Then, when you use the CMemoryState::DumpAllObjectsSince member function, each object allocated with DEBUG_NEW is shown with the filename and line number where it was allocated.
To use DEBUG_NEW, insert the following directive into your source files:
#define new DEBUG_NEW
Once you insert this directive, the preprocessor will insert DEBUG_NEW wherever you use new, and MFC does the rest. When you compile a release version of your program, DEBUG_NEW resolves to a simple new operation, and the filename and line number information is not generated.
再查看定義: #ifdef _DEBUG
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__) #else
#define DEBUG_NEW new
#endif
這樣就很清楚了,當(dāng)在debug模式下時(shí),,我們分配內(nèi)存時(shí)的new被替換成DEBUG_NEW,,而這個(gè)DEBUG_NEW不僅要傳入內(nèi)存塊的大小,還要傳入源文件名和行號(hào),,這就有個(gè)好處,,即當(dāng)發(fā)生內(nèi)存泄漏時(shí),我們可以在調(diào)試模式下定位到該問題代碼處,。若刪掉該句,,就不能進(jìn)行定位了。而在release版本下的new就是簡(jiǎn)單的new,,并不會(huì)傳入文件名和行號(hào)。
因此,,我們?cè)陂_發(fā)代碼階段,,保留上述代碼是值得的。
本文來自CSDN博客,,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/jiaoshuchun/archive/2010/01/13/5186743.aspx |
|