【问题标题】:Puzzled over memory leaks in MFC project that disappear if _CrtDumpMemoryLeaks() is never called如果 _CrtDumpMemoryLeaks() 从未被调用,MFC 项目中的内存泄漏就会消失
【发布时间】:2019-06-03 23:19:53
【问题描述】:

我有一个使用 Visual Studio 2017 编译的基于 MFC (C++) 对话框的项目。我添加了以下代码以在构建它时跟踪可能的内存泄漏:

在我的CWinApp 派生类被初始化之前,从ProjectName.cpp 内部开始。

#define _CRTDBG_MAP_ALLOC  
#include <stdlib.h>  
#include <crtdbg.h>
#include <Wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")


struct CatchMemLeaks{
    CatchMemLeaks()
    {
        HANDLE ghDebugLogFile = ::CreateFile(L".\\dbg_output.txt", 
            GENERIC_READ | GENERIC_WRITE, 
            FILE_SHARE_READ | FILE_SHARE_WRITE, 
            NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        //Enable logging into that file
        _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE));
        _CrtSetReportFile(_CRT_WARN, ghDebugLogFile);
        _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE));
        _CrtSetReportFile(_CRT_ERROR, ghDebugLogFile);
        _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE));
        _CrtSetReportFile(_CRT_ASSERT, ghDebugLogFile);


        //Try to break on the error reported
        _CrtSetBreakAlloc(75);
    }

    ~CatchMemLeaks()
    {
        if(_CrtDumpMemoryLeaks())
        {
            DWORD dwRespMsgBx;
            ::WTSSendMessage(NULL, ::WTSGetActiveConsoleSessionId(),
                L"MemLeak", lstrlen(L"MemLeak") * sizeof(WCHAR), 
                L"MemLeak", lstrlen(L"MemLeak") * sizeof(WCHAR),
                MB_OK | MB_ICONERROR | MB_SYSTEMMODAL,
                0, &dwRespMsgBx, TRUE);
        }
    }
};

CatchMemLeaks cml;



//Then the usual MFC CWinApp-app derived class stuff:
// CProjectNameApp

BEGIN_MESSAGE_MAP(CProjectNameApp, CWinApp)
    ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()


// CProjectNameApp construction

CProjectNameApp::CProjectNameApp()
{
    // support Restart Manager
    m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;

    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
}


// The one and only CProjectNameApp object

CProjectNameApp theApp;

//....

然后当项目运行然后退出时,我会触发我的WTSSendMessage

这给了我以下输出:

Detected memory leaks!
Dumping objects ->
{75} normal block at 0x0000029BA5EA75D0, 16 bytes long.
 Data: <   G            > B0 86 D0 47 F7 7F 00 00 00 00 00 00 00 00 00 00 
{74} normal block at 0x0000029BA5ECE930, 48 bytes long.
 Data: <0       0       > 30 E9 EC A5 9B 02 00 00 30 E9 EC A5 9B 02 00 00 
{73} normal block at 0x0000029BA5EA82F0, 16 bytes long.
 Data: <p  G            > 70 86 D0 47 F7 7F 00 00 00 00 00 00 00 00 00 00 
{72} normal block at 0x0000029BA5ECEA80, 48 bytes long.
 Data: <                > 80 EA EC A5 9B 02 00 00 80 EA EC A5 9B 02 00 00 
{71} normal block at 0x0000029BA5EA8070, 16 bytes long.
 Data: <   G            > 20 86 D0 47 F7 7F 00 00 00 00 00 00 00 00 00 00 
{70} normal block at 0x0000029BA5E98BA0, 120 bytes long.
 Data: <                > A0 8B E9 A5 9B 02 00 00 A0 8B E9 A5 9B 02 00 00 
Object dump complete.

但是在下一次调试运行时,当我添加上面代码中显示的_CrtSetBreakAlloc(75); 行时,错误75 上的断点永远不会触发,尽管输出仍然保持不变。

另一个有趣的发现是,如果我从 ~CatchMemLeaks 析构函数中删除 _CrtDumpMemoryLeaks() 函数,那些内存泄漏就会消失。

PS。我知道这是这个特定项目的特殊之处,因为如果我使用基于 MFC 对话框的股票应用程序尝试它,我不会得到相同的行为。

知道如何跟踪这些泄漏的来源吗?

【问题讨论】:

  • 我无法复制泄漏。您尚未展示完整的 MCVE。也可以使用CWinAppEx 而不是CWinApp,这在使用框架窗口时会有所不同。
  • @BarmakShemirani:哦,等等,我才意识到。谢谢。该类派生自CWinApp。自从 MFC wizzard 创建它以来,我没有更改它。虽然现在当我将CWinApp 替换为CWinAppEx 时,我在ENSURE(afxShellManager == NULL); 这一行的CShellManager 构造函数中得到了一个断言。你知道我该如何设置afxShellManager吗?这可能是我的问题。
  • 好的,找到导致ENSURE(afxShellManager == NULL) 断言的原因。如果我将CWinApp 替换为CWinAppEx,MFC 在InitInstance() 中有这行会触发它:CShellManager *pShellManager = new CShellManager; 将其注释掉会删除断言。我不知道它做了什么,但它不“喜欢”CWinAppEx-派生类。不过,不幸的是,这并不能解决原来的问题......
  • 再说一次关于您复制问题的问题。我也不能在股票项目中做到这一点。这就是具有挑战性的原因......
  • CatchMemLeaks cml; 之前使用最小的#pragma init_seg(compiler ) for cml 在 exe 中构造第一个并最后销毁

标签: c++ winapi memory-leaks mfc visual-studio-2017


【解决方案1】:

哎呀,我明白了。 (感谢 cmets 中的@RbMm!)关键是让这个泄漏检测代码在所有其他 CRT 和 MFC 构造函数和其他东西之前初始化(并在之后取消初始化)。诀窍是使用#pragma init_seg(compiler) 指令。我最初的错误是在定义了CWinApp 派生类的.cpp 文件中使用它。这会在应用退出时导致崩溃,因为 #pragma 指令适用于整个 .cpp 文件。

所以解决方案是为我的CatchMemLeaks 类创建一个单独的.h.cpp 文件,并在那里设置#pragma 指令,如下所示:

CatchMemLeaks.h文件:

#pragma once

//Only debugger builds
#ifdef _DEBUG

#define _CRTDBG_MAP_ALLOC  
#include <stdlib.h>  
#include <crtdbg.h>  

#include <Strsafe.h>
#include <Wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")


struct CatchMemLeaks{
CatchMemLeaks(int nMemLeakCodeToCatch);
~CatchMemLeaks();
};

#endif

CatchMemLeaks.cpp 文件:

#include "StdAfx.h"
#include "CatchMemLeaks.h"

//Only debugger builds
#ifdef _DEBUG

#pragma warning( push )
#pragma warning( disable : 4074)
#pragma init_seg(compiler)      //Make this code execute before any other code in this project (including other static constructors).
                                //This will also make its destructors run last.
                                //WARNING: Because of this do not call any CRT functions from this .cpp file!
#pragma warning( pop )

CatchMemLeaks cml(0);       //Set to (0) to monitor memory leaks, or to any other value to break on a specific leak number




CatchMemLeaks::CatchMemLeaks(int nMemLeakNumberToBreakOn)
{
    HANDLE ghDebugLogFile = ::CreateFile(.\\dbg_output.txt, 
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    //Enable logging into that file
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE));
    _CrtSetReportFile(_CRT_WARN, ghDebugLogFile);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE));
    _CrtSetReportFile(_CRT_ERROR, ghDebugLogFile);
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE));
    _CrtSetReportFile(_CRT_ASSERT, ghDebugLogFile);

    if(nMemLeakNumberToBreakOn)
    {
        _CrtSetBreakAlloc(nMemLeakNumberToBreakOn);
    }
}

CatchMemLeaks::~CatchMemLeaks()
{
    //Dump memory leaks, if any
    if(_CrtDumpMemoryLeaks())
    {
            DWORD dwRespMsgBx;
            ::WTSSendMessage(NULL, ::WTSGetActiveConsoleSessionId(),
                L"MemLeak", lstrlen(L"MemLeak") * sizeof(WCHAR), 
                L"MemLeak", lstrlen(L"MemLeak") * sizeof(WCHAR),
                MB_OK | MB_ICONERROR | MB_SYSTEMMODAL,
                0, &dwRespMsgBx, TRUE);
    }
}

#endif

然后最后将其包含在stdafx.h 文件中:

#include "CatchMemLeaks.h"  

【讨论】:

    【解决方案2】:

    您的CatchMemLeaks 类很可能在您程序中的所有其他对象被清理之前被创建然后销毁,因此实际上您报告的是误报(其他对象将在之后被清理)

    但如果没有完全运行的程序,就很难判断。

    【讨论】:

    • 嗯,这就是在所有对象创建并应该被释放之前和之后执行此操作的全部想法。我正在尝试捕获内存泄漏。这仍然不能解释为什么这在股票 MFC 项目中有效。
    • MFC 有一堆静态和全局变量,它们是生命周期超出“代码”范围的对象。
    猜你喜欢
    • 1970-01-01
    • 2010-10-12
    • 2011-03-17
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 2017-01-13
    • 2017-09-12
    相关资源
    最近更新 更多