MFC 调试方法

MFC 调试方法

Visual Studio 2013
其他版本
此主题尚未评级 评价此主题

如果要调试 MFC 程序,这些调试技术可能会有用。

本主题中

AfxDebugBreak

The TRACE macro

Memory leak detection in MFC

  • Tracking memory allocations

  • Enabling memory diagnostics

  • Taking memory snapshots

  • Viewing memory statistics

  • Taking object dumps

    • Interpreting memory dumps

    • Customizing object dumps

    Reducing the size of an MFC Debug build

    • Building an MFC app with debug information for selected modules

AfxDebugBreak

MFC 提供了一种特殊 AfxDebugBreak 在源代码中的硬编码断点的函数:

AfxDebugBreak( );

在英特尔平台上, AfxDebugBreak生成以下代码,而不是内核代码的源中的哪些符代码:

_asm int 3

在其他平台上, AfxDebugBreak仅调用 DebugBreak.

一定要删除AfxDebugBreak语句创建发布时创建或使用 #ifdef _DEBUG以将其括起。

In this topic

TRACE 宏

若要显示邮件从您的程序在调试器中输出窗口,您可以使用 ATLTRACE 宏或 MFC 跟踪宏。 像断言,跟踪宏仅在程序的调试版本中处于活动状态,在发布版本中编译时消失。

下面的示例演示了几种您可以使用跟踪宏。 像 printf、 跟踪宏可以处理参数的数量。

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);


有关详细信息跟踪 宏,请参见 诊断服务。

In this topic

在 MFC 中检测内存泄漏

MFC 提供类和函数检测已分配但从未释放的内存。

跟踪内存分配

在 MFC 中,您可以使用宏 DEBUG_NEW 代替运算符来帮助定位内存泄漏。 在您的程序的调试版本DEBUG_NEW跟踪的为其分配的每个对象的文件名和行号。 当您编译您的程序的发布版本DEBUG_NEW解析为一个简单的 操作而无需文件的名称和行号信息。 因此,您在程序的发行版中支付任何速度损失。

如果您不希望重写整个程序可以使用DEBUG_NEW代替 ,您可以在源文件中定义此宏:

#define new DEBUG_NEW

执行操作时对象转储,每个对象分配与 DEBUG_NEW将显示的文件和行号被分配,使您可以查明内存泄漏源。

MFC 框架中的调试版本使用DEBUG_NEW自动,但您的代码不会。 如果您想要的好处 DEBUG_NEW,则必须使用 DEBUG_NEW显式或 #define 新 ,如上所示。

In this topic

启用内存诊断

您可以使用内存诊断功能之前,您必须启用诊断跟踪。

若要启用或禁用内存诊断

  • 调用全局函数 AfxEnableMemoryTracking 若要启用或禁用诊断内存分配器。 因为内存诊断程序在默认情况下,调试库中,您通常会使用此函数可暂时关闭它们,这会增加程序执行的速度,并减少诊断输出。

若要选择特定内存诊断功能,用 afxMemDF

  • 如果希望对内存诊断功能进行更精确地控制,您可以有选择地打开单个内存诊断功能打开和关闭通过设置 MFC 全局变量的值 afxMemDF。 此变量可以具有下列值所指定的枚举类型 afxMemDF

    说明

    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 块,使其仅在程序的 Debug 版本中编译。

    现在,您知道存在内存泄漏,则可以使用另一个成员函数, 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

大多数行的开始处的括号中的数字指定对象的分配的顺序。 最近分配的对象具有最高编号和转储的顶部会出现。

若要从对象转储中获取最多信息,可以重写Dump的任何成员函数 CObject-派生的对象,可以自定义对象转储。

您可以在特定内存分配上设置断点,通过设置全局变量_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代表的地址块,后面跟着的对象,它由输出内容 CPerson::Dump 当调用 DumpAllObjectsSince。

您可以猜出与块号 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,或 如果 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,您可以重写 Dump成员函数来提供附加的信息,当您使用 DumpAllObjectsSince 与转储对象和输出窗口。

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 的调试版本提供的预定义CDumpContext对象命名为 afxDump,将输出发送到调试器。

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 调试版本的大小

一个大型的 MFC 应用程序的调试信息会占用大量的磁盘空间。 可以使用下列过程之一来减小大小:

  1. 使用 MFC 库重新/Z7、/Zi、/ZI(调试信息格式)选项,而不是 /Z7这些选项会生成单个程序数据库 (PDB) 文件,其中包含有关整个库中,从而减少冗余和节省空间的调试信息。

  2. 重新生成不包含调试信息的 MFC 库 (没有/Z7、/Zi、/ZI(调试信息格式)选项)。 在这种情况下,缺少调试信息将防止您使用 MFC 库代码内, 大多数调试器功能,但由于 MFC 库已完全调试,这可能不是问题。

  3. 构建您自己的应用程序用选定模块的调试信息,仅按如下所述。

In this topic

构建用选定模块的调试信息的 MFC 应用程序

生成选定具有 MFC 调试库的模块使您可以使用单步执行,并在这些模块中的其他调试功能。 此过程可以使用的两种调试和发行模式的 Visual C++ 生成文件中,因此也需要以下步骤 (但还提出"全部重新生成"中介绍的更改 必要时需要完整的发布版本时)。

  1. 在解决方案资源管理器中,选择项目。

  2. 查看 菜单中选择 属性页

  3. 首先,您将创建一个新的项目配置。

    1. 在 < 项目 > 属性页 对话框中,单击 配置管理器按钮。

    2. 在配置管理器对话框,在网格中找到您的项目。 配置 列中,选择 < 新建...>

    3. 在新项目配置的对话框,在键入您的新配置,如"部分调试,"名称 项目配置名称框。

    4. 从此处复制设置 列表中选择 版本

    5. 单击确定 关闭 新项目配置对话框。

    6. 关闭配置管理器对话框。

  4. 现在,您将设置为整个项目的选项。

    1. 属性页 对话框中,在 配置属性 文件夹中选择 常规类别。

    2. 在项目设置网格中,展开项目默认值 (如有必要)。

    3. 项目默认值,查找 使用的 MFC。 右侧的网格列中将显示当前的设置。 单击当前设置,并将其更改为在静态库中使用 MFC

    4. 在左窗格中的属性页 对话框中打开 C/c + + 文件夹,然后选择预处理程序。 在属性网格中,找到预处理程序定义和替换"调试" 与"_DEBUG"。

    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 版本的库,则必须定义调试。 编写外部生成文件的详细信息,请参阅 NMAKE 参考。

In this topic

请参见

其他资源

调试 Visual C++

http://msdn.microsoft.com/zh-cn/library/7sx52ww7.aspx/html

你可能感兴趣的:(C&C++/MFC)