Озадаченный утечками памяти в проекте MFC, которые исчезают, если _CrtDumpMemoryLeaks () никогда не вызывается - PullRequest
0 голосов
/ 09 января 2019

У меня есть проект на основе диалогового окна MFC (C ++), скомпилированный с Visual Studio 2017. Я добавил следующий код для отслеживания возможных утечек памяти при его создании:

Из ProjectName.cpp до инициализации моего CWinApp класса.

#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:

enter image description here

Что дает мне следующий вывод:

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 никогда не срабатывает, хотя выходные данные остаются прежними.

Тогда еще одно интересное открытие заключается в том, что если я удалю функцию _CrtDumpMemoryLeaks() из моего деструктора ~CatchMemLeaks, утечки памяти исчезнут.

PS. Я знаю, что это что-то особенное для этого конкретного проекта, потому что я не получаю такого же поведения, если пытаюсь сделать это с помощью стандартного приложения на основе MFC-диалога.

Есть идеи, как отследить, откуда происходят эти утечки?

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Ох, стреляй, я понял. (Благодаря @ RbMm в комментариях!) Подвох заключается в том, чтобы этот код обнаружения утечек инициализировался до (и не инициализировался после) всех других конструкторов CRT и MFC и прочего. Хитрость заключается в использовании директивы #pragma init_seg(compiler). Моя первоначальная ошибка заключалась в том, чтобы использовать его в файле .cpp, где был определен класс CWinApp. Это вызвало сбой при выходе из приложения, поскольку директива #pragma применяется ко всему файлу .cpp.

Таким образом, решение состоит в том, чтобы создать отдельные файлы .h и .cpp для моего класса CatchMemLeaks и установить там директиву #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 file:

#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"  
0 голосов
/ 09 января 2019

Скорее всего, ваш класс CatchMemLeaks создан и затем уничтожен до того, как все другие объекты в вашей программе очищены, так что в действительности вы сообщаете о ложноположительном результате (другие объекты будут очищены после)

Но трудно сказать без полностью запущенной программы.

...