MFC 調試方法


MFC 提供特殊的 AfxDebugBreak 函數,以供在源代碼中對斷點進行硬編碼:

 
 
AfxDebugBreak( );

在 Intel 平台上,AfxDebugBreak 將生成以下代碼,它在源代碼而不是內核代碼中中斷:

 
 
_asm int 3

在其他平台上,AfxDebugBreak 僅調用 DebugBreak。

確保在創建發布版本時移除 AfxDebugBreak 語句,或使用 #ifdef _DEBUG 環繞這些語句。

In this topic

 

TRACE 宏
 
 

 

若要在調試器的“輸出”窗口中顯示來自程序的消息,可以使用 ATLTRACE 宏或 MFC TRACE 宏。 斷言類似,跟蹤宏只在程序的“Debug”版本中起作用,在“Release”版本中編譯時將消失。

下面的示例顯示幾種 TRACE 宏的用法。 與 printf 類似,TRACE 宏可處理許多參數。

 
 
int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );

TRACE( "The value of x is %d\n", x );

TRACE( "x = %d and y = %d\n", x, y );

TRACE( "x = %d and y = %x and z = %f\n", x, y, z );

TRACE 宏可正確處理 char* 參數和 wchar_t* 參數。 下面的示例說明如何將 TRACE 宏與不同字符串參數類型配合使用。

 
 
TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);

TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);

TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);

有關 TRACE 宏的更多信息,請參見診斷服務

In this topic

 

MFC 提供一些類和函數來檢測曾經被分配但從未釋放的內存。

在 MFC 中,可以使用 DEBUG_NEW 宏代替 new 運算符來幫助定位內存泄漏。 在程序的“Debug”版本中,DEBUG_NEW 將為所分配的每個對象跟蹤文件名和行號。 當編譯程序的“Release”版本時,DEBUG_NEW 將解析為不包含文件名和行號信息的簡單 new 操作。 因此,在程序的“Release”版本中不會造成任何速度損失。

如果不想重寫整個程序來使用 DEBUG_NEW 代替 new,則可以在源文件中定義下面的宏:

 
 
#define new DEBUG_NEW

當進行對象轉儲時,用 DEBUG_NEW 分配的每個對象均將顯示被分配到的文件和行號,使你可以查明內存泄漏源。

MFC 框架的“Debug”版本自動使用 DEBUG_NEW,但代碼不自動使用它。 如果希望利用 DEBUG_NEW 的好處,則必須顯式使用 DEBUG_NEW或 #define new,如上所示。

In this topic

必須先啟用診斷跟蹤,然后才能使用內存診斷功能。

啟用或禁用內存診斷

  • 調用全局函數 AfxEnableMemoryTracking 來啟用或禁用診斷內存分配器。 由於默認情況下內存診斷在調試庫中是打開的,所以通常會使用該函數暫時關閉內存診斷,這會提高程序執行速度並減少診斷輸出。

使用 afxMemDF 選擇特定內存診斷功能

  • 如果希望對內存診斷功能進行更精確的控制,可以通過設置 MFC 全局變量 afxMemDF 的值,來有選擇地打開和關閉單個內存診斷功能。該變量可以具有下列值(由枚舉類型 afxMemDF 所指定)。

     

    Value

    描述

    allocMemDF

    打開診斷內存分配器(默認)。

    delayFreeMemDF

    在調用 delete 或 free 時延遲釋放內存,直到程序退出。 這將使你的程序分配可能的最大內存量。

    checkAlwaysMemDF

    每次分配或釋放內存時均調用 AfxCheckMemory

    可以通過執行邏輯 OR 操作來組合使用這些值,如下所示:

    C++
     
    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

In this topic

 

  1. 創建一個 CMemoryState 對象並調用 CMemoryState::Checkpoint 成員函數。 這將創建第一個內存快照。

  2. 在程序執行了其內存分配和釋放操作以后,創建另一個 CMemoryState 對象,並為該對象調用 Checkpoint。 這將得到內存使用的第二個快照。

  3. 創建第三個 CMemoryState 對象,並調用其 CMemoryState::Difference 成員函數,同時將前兩個 CMemoryState 對象作為參數提供。如果這兩個內存狀態之間有差異,則 Difference 函數將返回非零值。 這指示有些內存塊尚未被釋放。

    本示例顯示相應的代碼:

     
     
    // Declare the variables needed
    #ifdef _DEBUG
        CMemoryState oldMemState, newMemState, diffMemState;
        oldMemState.Checkpoint();
    #endif
    
        // Do your memory allocations and deallocations.
        CString s("This is a frame variable");
        // The next object is a heap object.
       CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    
    #ifdef _DEBUG
        newMemState.Checkpoint();
        if( diffMemState.Difference( oldMemState, newMemState ) )
        {
            TRACE( "Memory leaked!\n" );
        }
    #endif
    

    請注意,內存檢查語句由 #ifdef_DEBUG#endif 塊括起來,這樣就只能在程序的調試版本中對它們進行編譯。

    既然已經知道存在內存泄漏,便可以使用另一個成員函數 CMemoryState::DumpStatistics,該函數將有助於對其進行定位。

In this topic

CMemoryState::Difference 函數監視兩個內存狀態對象,並檢測起始狀態和結束狀態之間未從堆釋放的所有對象。 在拍攝內存快照並使用CMemoryState::Difference 對它們進行比較后,可以調用 CMemoryState::DumpStatistics 來獲取有關尚未釋放的對象的信息。

請看下面的示例:

 
 
if( diffMemState.Difference( oldMemState, newMemState ) )
{
   TRACE( "Memory leaked!\n" );
   diffMemState.DumpStatistics();
}

從該示例得出的轉儲示例如下所示:

 
 
0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes

可用塊是 afxMemDF 設置為 delayFreeMemDF 時延遲釋放的塊。

第二行中顯示的普通對象塊仍在堆中保持分配狀態。

非對象塊包括通過 new 分配的數組和結構。 在此例中,堆中分配了四個非對象塊,但均未釋放。

Largest number used 給出程序在任意時候所使用的最大內存。

Total allocations 給出程序所使用的內存總量。

In this topic

在 MFC 程序中,可以使用 CMemoryState::DumpAllObjectsSince 來轉儲堆上尚未釋放的所有對象的描述。 DumpAllObjectsSince 轉儲從最后一個 CMemoryState::Checkpoint 以來分配的所有對象。 如果未發生 Checkpoint 調用,則 DumpAllObjectsSince 將轉儲當前在內存中的所有對象和非對象。

說明 說明

必須先啟用診斷跟蹤,然后才能使用 MFC 對象轉儲。

說明 說明

程序退出時 MFC 將自動轉儲所有泄漏的對象,因此不必創建代碼在該點轉儲對象。

以下代碼通過比較兩個內存狀態來測試內存泄漏,並在檢測到泄漏時轉儲所有對象。

 
 
if( diffMemState.Difference( oldMemState, newMemState ) )
{
   TRACE( "Memory leaked!\n" );
   diffMemState.DumpAllObjectsSince();
}

轉儲的內容如下所示:

 
 
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

大多數行開始處的大括號中的數字指定對象的分配順序。 最近分配的對象具有最高編號,並顯示在轉儲的頂部。

若要從對象轉儲獲取最大信息量,可以重寫 CObject 派生的任何對象的 Dump 成員函數,以自定義對象轉儲。

通過將全局變量 _afxBreakAlloc 設置為大括號中顯示的數字,可以在特定內存分配上設置斷點。 如果重新運行程序,調試器將在該分配發生時中斷執行。 然后可以查看調用堆棧,以了解程序是怎樣到達該點的。

C 運行庫有一個類似的函數 _CrtSetBreakAlloc,可用於 C 運行時分配。

In this topic

查看此對象轉儲的更詳細信息:

 
 
{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

生成該轉儲的程序只有兩個顯式分配,一個在框架上,另一個在堆上:

 
 
// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );

CPerson 構造函數取三個參數(指向 char 的指針),用於初始化 CString 成員變量。 在內存轉儲中,可以看到 CPerson 對象以及三個非對象塊(3、4 和 5)。 它們保存 CString 成員變量的字符,並且在調用 CPerson 對象析構函數時不會被刪除。

塊號 2 是 CPerson 對象本身。 $51A4 表示塊地址,其后是對象內容,該內容在 DumpAllObjectsSince 調用 CPerson::Dump 時由后者輸出。

可以因為塊號 1 的序號和大小(與框架 CString 變量中的字符數匹配)而猜測其與 CString 框架變量相關聯。 框架上分配的變量在框架超出范圍后自動釋放。

框架變量

一般情況下,你不必擔心與框架變量關聯的堆對象,因為它們在框架變量超出范圍后被自動釋放。 為避免內存診斷轉儲混亂,應將對Checkpoint 的調用定位在框架變量的范圍以外。 例如,在前面的分配代碼周圍放置范圍括號,如下所示:

 
 
oldMemState.Checkpoint();
{
    // Do your memory allocations and deallocations ...
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
}
newMemState.Checkpoint();

放置了范圍括號后,該示例的內存轉儲如下所示:

 
 
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

非對象分配

請注意,一些分配是對象分配(如 CPerson),另外一些則是非對象分配。" “非對象分配”是用於非 CObject 派生的對象的分配,或者基元 C 類型的分配(如 char、int 或 long)。 如果 CObject 派生的類分配額外的空間(例如用於內部緩沖區),則那些對象將既顯示對象分配,也顯示非對象分配。

防止內存泄漏

注意,在上面的代碼中,與 CString 框架變量關聯的內存塊已自動釋放,因而不作為內存泄漏顯示。 與范圍規則關聯的自動釋放負責處理與框架變量關聯的大多數內存泄漏。

但對於在堆中分配的對象,則必須顯式刪除對象以防止內存泄漏。 若要清理上個示例中的最后一個內存泄漏,請刪除堆中分配的 CPerson 對象,如下所示:

 
 
{
    // Do your memory allocations and deallocations.
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    delete p;
}

In this topic

當從 CObject 派生類時,在使用 DumpAllObjectsSince 將對象轉儲到“輸出”窗口時,可以重寫 Dump 成員函數以提供附加信息。

Dump 函數將對象的成員變量的文本化表示形式寫入轉儲上下文 (CDumpContext)。 轉儲上下文類似於 I/O 流。 可以使用追加運算符 (<<) 向CDumpContext 發送數據。

重寫 Dump 函數時,應先調用 Dump 的基類版本以轉儲基類對象的內容。 然后為派生類的每個成員變量輸出文本化說明和值。

Dump 函數的聲明如下所示:

 
 
class CPerson : public CObject
{
public:
#ifdef _DEBUG
    virtual void Dump( CDumpContext& dc ) const;
#endif

    CString m_firstName;
    CString m_lastName;
    // And so on...
};

由於對象轉儲只在調試程序時有意義,所以 Dump 函數的聲明用 #ifdef _DEBUG / #endif 塊括起來。

在下面的示例中,Dump 函數先為其基類調用 Dump 函數。 然后,它將每個成員變量的簡短說明與該成員的值一起寫入診斷流。

 
 
#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 參數以指定轉儲輸出的目的地。 MFC 的“Debug”版本提供名為 afxDump 的預定義 CDumpContext 對象,它將輸出發送到調試器。

 
 
CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif

In this topic

 

大型 MFC 應用程序的調試信息會占用大量磁盤空間。 你可以使用以下過程之一減小該大小:

  1. 使用 /Z7、/Zi、/ZI(調試信息格式) 選項而不是 /Z7 來重新生成 MFC 庫。 這些選項生成單個程序數據庫 (PDB) 文件,該文件包含整個庫的調試信息,減少了冗遇並節省了空間。

  2. 重新生成沒有調試信息的 MFC 庫(沒有 /Z7、/Zi、/ZI(調試信息格式) 選項)。 在此情況下,缺少調試信息將妨礙你在 MFC 庫代碼內使用大多數調試器功能,但由於 MFC 庫已完全調試,所以可能不會有問題。

  3. 生成你自己的只帶有選定模塊的調試信息的應用程序,如下所述。

In this topic

生成帶有 MFC 調試庫的選定模塊以后,你便可以在這些模塊中使用單步執行和其他調試功能。 該過程同時利用 Visual C++ 生成文件的“Debug”模式和“Release”模式,從而使得有必要進行下面所描述的更改(也使得在需要完全發布版本時必須進行“全部重新生成”)。

  1. 在“解決方案資源管理器”中,選擇項目。

  2. “視圖”菜單中選定“屬性頁”。

  3. 首先,將創建一個新的項目配置。

    1. “<項目> 屬性頁”對話框中,單擊“配置管理器”按鈕。

    2. “配置管理器”對話框中,在網格中定位你的項目。 “配置”列中,選擇“<新建...>”。

    3. “新建項目配置”對話框中的“項目配置名”框中鍵入新配置的名稱,如“Partial Debug”(部分調試)。

    4. “從此處復制設置”列表中,選擇“Release”。

    5. 單擊“確定”以關閉“新建項目配置”對話框。

    6. 關閉“配置管理器”對話框。

  4. 現在,將為整個項目設置選項。

    1. “屬性頁”對話框中的“配置屬性”文件夾下選定“常規”類別。

    2. 在項目設置網格中展開“項目默認值”(如有必要)。

    3. “項目默認值”下找到“MFC 的使用”。 當前設置將顯示在網格的右列中。 單擊當前設置並將它更改為“在靜態庫中使用 MFC”。

    4. “屬性頁”對話框的左窗格中,打開“C/C++”文件夾並選定“預處理器”。 在“屬性”網格中找到“預處理器定義”,並用“_DEBUG”替換“NDEBUG”。

    5. “屬性頁”對話框的左窗格中,打開“鏈接器”文件夾並選定“輸入”類別。 在“屬性”網格中找到“附加依賴項”。 “附加依賴項”設置中,鍵入“NAFXCWD.LIB”和“LIBCMT”。

    6. 單擊“確定”以保存新的生成選項並關閉“屬性頁”對話框。

  5. “生成”菜單中選定“重新生成”。 這將從模塊中移除所有調試信息,但不影響 MFC 庫。

  6. 現在必須將調試信息添加回應用程序中的選定模塊。 請記住,只能在已用調試信息編譯了的模塊中設置斷點和執行其他調試器函數。 對於要包括調試信息的每個項目文件,執行以下步驟:

    1. 在“解決方案資源管理器”中,打開位於你的項目下的“源文件”文件夾。

    2. 選擇要為其設置調試信息的文件。

    3. “視圖”菜單中選定“屬性頁”。

    4. “屬性頁”對話框中的“配置設置”文件夾下,打開“C/C++”文件夾,然后選定“常規”類別。

    5. 在“屬性”網格中找到“調試信息格式”

    6. 單擊“調試信息格式”設置並為調試信息選擇所需選項(通常為“/ZI”)。

    7. 如果要使用應用程序向導生成的應用程序或具有預編譯頭,則在編譯其他模塊以前必須關閉預編譯頭或重新編譯預編譯頭。 否則,將收到警告 C4650 和錯誤消息 C2855。 通過更改“<項目> 屬性”對話框中的“創建/使用預編譯頭”設置,可關閉預編譯頭(該設置位於“配置屬性”文件夾下的“C/C++”子文件夾中的“預編譯頭”類別中)。

  7. “生成”菜單中選定“生成”以重新生成已過期的項目文件。

作為本主題中所描述技術的替換技術,可以使用外部生成文件為每個文件定義單個選項。 在這種情況下,若要鏈接 MFC 調試庫,必須為每個模塊都定義 _DEBUG 標志。 如果想使用 MFC 發布庫,必須定義了 NDEBUG。 有關編寫外部生成文件的更多信息,請參見 NMAKE 參考

from: https://msdn.microsoft.com/zh-cn/library/7sx52ww7(v=vs.120).aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM