Обнаружение утечек памяти во время d-тор объектов - PullRequest
2 голосов
/ 01 января 2012

Мое приложение является реализацией, основанной на dll, и, вероятно, теряет память при выгрузке.Я заметил это во время циклов выгрузки / перезагрузки (когда процесс хостинга не прерывается).Виртуальная память процесса хостинга увеличивается.

Я прошел проверку кода, чтобы попытаться найти утечку кода, но не нашел ее.

Я ищу другие методы обнаруженияутечки памяти при выгрузке (когда объекты разрушаются).

EDIT : я использую платформу win32 (XP).

есть ли у вас опыт использования таких инструментов / процедур?Спасибо

Ответы [ 4 ]

4 голосов
/ 01 января 2012

Что я делал давным-давно, так это:

Я написал свои mymalloc, myrealloc и myfree (и перегружен new и delete, чтобы онивызовите мои функции.) Затем я написал макросы malloc и realloc, которые вызывали mymalloc и myrealloc, передавая им __FILE__ и __LINE__.mymalloc сделал следующее: он вызвал функцию malloc стандартной библиотеки, выделив немного больший блок, и вставил __FILE__ и __LINE__ в этот блок.Кроме того, он сохранял все выделенные блоки в связанном списке, чтобы иметь возможность проходить их позже.

При выходе из программы я просматривал список блоков, которые не были освобождены, и распечатывалфайл и строка, которые были причиной утечек памяти.

В настоящее время я предполагаю, что будут готовые инструменты, которые вы могли бы сделать для вас.

2 голосов
/ 01 января 2012

вы не указали, какую платформу вы ищете. Я разработчик Windows, поэтому я могу рекомендовать только Windows решения. Если это то, с чем вы работаете, есть ряд коммерческих и бесплатных инструментов. Немногие, с которыми я лично работал: Purify, BoundsChecker, UMDH, LeakDiag, DebugDiag.

Из них я обычно предпочитаю UMDH. Он бесплатный и входит в состав Средства отладки для Windows (DTW) . Я обнаружил, что он на самом деле более надежный и менее ресурсоемкий, чем большинство других, включая профессиональные инструменты. Его очень просто использовать, а документацию можно найти в файле .chm, который поставляется с установкой DTW. В конце концов, я лично обнаружил, что UMDH имеет очень высокое отношение сигнал / шум по сравнению со многими другими инструментами.

DebugDiag - еще одна хорошая альтернатива. Насколько я могу судить, он использует почти те же API, что и UMDH, но его использование несколько сложнее, поскольку оно основано на интерфейсе пользователя, а не на командной строке, поэтому для достижения цели обычно требуется больше кликов, но для кого-то нового я бы рекомендовал его UMDH.

UPDATE:

Интересно, что большинство предпочитало вставлять пользовательские хуки в malloc / free, а затем добавлять еще больше кода для пользовательских хуков в операторы new / delete.

Я настоятельно рекомендую вам взглянуть на UMDH и узнать, как он работает, даже если вы не находите в этом особого необходимости. В основе всех распределений памяти лежат функции Windows, HeapAlloc / HeapFree. Microsoft, ожидая необходимости в методах обнаружения утечек, уже предоставила хуки, которые мы можем использовать на этом корневом уровне.

Это другие преимущества использования UMDH по сравнению с пользовательскими перехватчиками:

  • Вы получаете полную трассировку стека каждого распределения, а не только то, что предоставляется __FILE__ и __LINE __
  • Он уже имеет полную отчетность и агрегирование статистики, которую вы должны написать поверх простого перехвата malloc / free. Вы получаете количество распределений для каждой трассы, количество байтов, выделенных каждой трассой, и список выделенных буферов памяти, так что вы можете фактически проанализировать, какие типы данных были пропущены.
  • Обнаружение утечек malloc / free, которые произошли в чужом коде, а не в конкретно DLL под вашим контролем
  • Обнаружение утечек из других функций выделения памяти, таких как CoTaskMemAlloc или SysStringAlloc
  • Обнаружение утечек COM-объектов, которые не были должным образом устранены
  • Обнаружение логических ошибок в вашем коде при вызове стороннего API, который возвращает буфер, который вы забыли освободить.
  • Вы можете использовать UMDH мгновенно с любой кодовой базой, не добавляя пользовательский код снова и снова.
  • Принятый вами метод работает только в среде отладки. UMDH может использоваться точно так же, без каких-либо изменений кода в производственных системах.

Практически каждый раз, когда наблюдается тенденция к увеличению использования памяти, инструмент сообщит вам, откуда он. Большую часть времени я могу обнаружить утечку в течение 10 минут, если ее можно воспроизвести на компьютере разработчика (при отладке рабочего кода это занимает немного больше времени, поскольку файлы символов должны соответствовать, иногда вручную).

Итак, если вы получаете все это совершенно бесплатно с установками DTW (у которых, кстати, есть и другие потрясающие функции отладки), почему люди предпочитают создавать свой собственный код обнаружения утечек?

1 голос
/ 01 января 2012

В дополнение к ответу Майка (переопределяя Malloc) вы также можете переопределить операторы new и delete в Visual Studio.

Отказ от ответственности: я нашел этот код в сети еще в 2004 году и включил его в проект C ++.Я не знаю, оригинальный источник

Ниже приведен пример кода (который я включил в качестве заголовочного файла, memleak.h).Это довольно старый код, поэтому возможны ошибки при компиляции!Однако он иллюстрирует, как переопределить новое и удалить.Также дамп освобождает память в файл.Этот код вступает в силу, если определение _DEBUG включено в ваш код.

С уважением,

#include <iostream>
#include <list>
using namespace std;

//void DumpUnfreed();
//void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum);
//void RemoveTrack(DWORD addr);

typedef struct 
{
    DWORD   address;
    DWORD   size;
    char    file[64];
    DWORD   line;
} ALLOC_INFO;      

typedef list<ALLOC_INFO*> AllocList;   

AllocList *allocList; 

void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum)
{
    ALLOC_INFO *info;         
    if(!allocList) 
    {
        allocList = new(AllocList);
    }         
    info = new(ALLOC_INFO);
    info->address = addr;
    strncpy(info->file, fname, 63);
    info->line = lnum;
    info->size = asize;
    allocList->insert(allocList->begin(), info);
};      

void RemoveTrack(DWORD addr)
{
    AllocList::iterator i;        
    if(!allocList)
        return;
    for(i = allocList->begin(); i != allocList->end(); i++)
    {
        if((*i)->address == addr)
        {
            allocList->remove((*i));
            break;
        }
    }
};

void DumpUnfreed()
{
    AllocList::iterator i;
    DWORD totalSize = 0;
    char buf[1024];   
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
    OutputDebugString("DSP.DLL: Detecting unfreed memory...\n");
    if(!allocList)
    {
        OutputDebugString("No memory allocations were tracked!\n");
        return;       
    }
    for(i = allocList->begin(); i != allocList->end(); i++) 
    {
        sprintf(buf, "%-50s:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",
            (*i)->file, (*i)->line, (*i)->address, (*i)->size);
        OutputDebugString(buf);
        totalSize += (*i)->size;
    }
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
    sprintf(buf, "DSP.DLL Total Unfreed: %d bytes\n", totalSize);
    OutputDebugString(buf);
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
};

#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size,
                                         const char *file, int line)
{
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);
};
inline void __cdecl operator delete(void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
inline void * __cdecl operator new[ ] (unsigned int size, const char *file, int line)
{  
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);                            
};
inline void __cdecl operator delete[ ] (void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
#endif

#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW
1 голос
/ 01 января 2012

Если вы можете скомпилировать свой код на поддерживаемой платформе , вам непременно следует попробовать инструмент Valgrind Memcheck . Прочтите Краткое руководство Valgrind , чтобы узнать как.

Вот краткий пример ...

Источник:

$ cat -n leaky.cpp 
     1  struct leaky
     2  {
     3      leaky()
     4          :bytes(new char[256])
     5      {
     6      }
     7  
     8      char* bytes;
     9  };
    10  
    11  int main()
    12  {
    13      leaky sieve;
    14      return sizeof sieve;
    15  }
    16  

Сложение:

$ make leaky
g++ -Wall -Wextra -Wshadow -pedantic -Wno-long-long -Wfloat-equal -Wcast-qual -g -I/opt/local/include -Weffc++ -Wall -I /opt/local/include -L/opt/local/lib  leaky.cpp   -o leaky

Проверка:

$ valgrind --leak-check=full ./leaky
==85800== Memcheck, a memory error detector
==85800== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==85800== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==85800== Command: ./leaky
==85800== 
==85800== 
==85800== HEAP SUMMARY:
==85800==     in use at exit: 2,367 bytes in 33 blocks
==85800==   total heap usage: 33 allocs, 0 frees, 2,367 bytes allocated
==85800== 
==85800== 256 bytes in 1 blocks are definitely lost in loss record 6 of 9
==85800==    at 0xB823: malloc (vg_replace_malloc.c:266)
==85800==    by 0x5768D: operator new(unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800==    by 0x576DA: operator new[](unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800==    by 0x100000EE7: leaky::leaky() (leaky.cpp:4)
==85800==    by 0x100000EB3: main (leaky.cpp:13)
==85800== 
==85800== LEAK SUMMARY:
==85800==    definitely lost: 256 bytes in 1 blocks
==85800==    indirectly lost: 0 bytes in 0 blocks
==85800==      possibly lost: 0 bytes in 0 blocks
==85800==    still reachable: 2,111 bytes in 32 blocks
==85800==         suppressed: 0 bytes in 0 blocks
==85800== Reachable blocks (those to which a pointer was found) are not shown.
==85800== To see them, rerun with: --leak-check=full --show-reachable=yes
==85800== 
==85800== For counts of detected and suppressed errors, rerun with: -v
==85800== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 1 from 1)
...