
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.97.9.173] |
![]() |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Язык Си славится своей сложностью, и "неприкрытостью" указателей. Поэтому нет ничего странного в том, что утечка памяти так и наровит случиться, и программист часто сталкивается с необходимостью обнаружить и устранить её. Утечка памяти происходит когда программа выделяет динамическую память, и забывает её освободить. Таким образом, чем дольше программа работает, тем больше памяти использует. Рано или поздно память закончится, что приведёт к замедлению системы (и, как показывает опыт, к непредсказуемым глюкам в Windows NT). Почему-то считается, что обнаружить утечку очень сложно (ведь на первый взгляд программа работает правильно) а обнаружить в каком месте программы она происходит - ещё сложнее.
Чтобы сэкономить человеку время и силы (пессимисты утверждают, что для того чтобы отнять у ЭВМ процессорное время и память) были разработаны специальные средства, помогающие найти ошибку: линты, профилеровщики, библиотеки. Линтами (Lint) называют программы, анализирующие исходный код, и находящие в нём места потенциальных ошибок. Не стоит ждать от линтов эффективности. Профилеровщики запускают вашу программу в виртуальной среде, и по окончании смотрят вся ли память освободилась. Библиотеки - подменяют функции менеджера памяти (malloc, calloc, realloc и free) на свои, ведущие подсчет выделяемой памяти. Я смотрел одну такую библиотеку под названием dmalloc. Она состоит из сотен (если не тысяч) строчек кода, снабжена файлом документации (HTML) размером более 150 kB, а на сайте (dmalloc.com) приведён длинный перечень операционных систем, в которых эта библиотека работает (Windows среди них нет, зато есть Cygwin). Неужели всё так сложно? Я ничего не придумывал, но естественный ход мысли (он был на 100% естественный, потому что тогда я ещё не знал про вышеописанные средства, и принцип их действия) привел меня к следующему решению. Подменить функции менеджера памяти при помощи препроцессора, примерно так: ![]() ![]() static int memallocated=0; #ifndef NDEBUG # define malloc(size) malloc((memallocated+=size, size)) #endif Теперь ниже по коду выделение памяти функцией malloc будет увеличивать значение переменной memallocated (её легко отслеживать в отладчике). А функция free значит должна уменьшать memallocated. Но насколько? Ведь free не передаётся параметр, содержащий количество байт для освобождения. Я решил эту задачу при помощи хранения размера выделенной области памяти в самой области. То есть выделяется не size байт, а size+sizeof(int), в начало записывается размер, и возвращается указатель не на начало области, а на позицию после записанного нами размера, т.е. не p, а p+sizeof(int). Таким образом, если в конце программы переменная memallocated содержит не ноль, то мы можем заключить, что в нашей программе утечка памяти. Но где эту утечку искать? Чтобы найти её было легче, я пошёл ещё дальше, и добавил алгоритм, запоминающий на какой строчке кода и в каком файле выделялся каждый блок памяти. Вот что получилось. ![]() ![]() /* memtraces.h - replaces functions malloc, calloc, realloc and free with another ones that count memory being allocated. Also defines macro that allows to get the size of a memory block (memsize). Date: 2 August 2006 Author: Jeremiah Shaulov Lisence: GPL */ #ifndef MEMTRACES_INCLUDED #define MEMTRACES_INCLUDED #include <stdio.h> #include <stdlib.h> #include <string.h> static int memallocated=0, mempeak=0, memtraces_cnt=0, pmemtemp; static struct {void *p; int size; int line; char file[20];} *memtraces_var=NULL; static FILE *memtraces_log=NULL; #define memsize(p) ((p)==NULL ? 0 : ((int*)(p))[-1]) void *realloc2(void *p, int size) { int *p2; if (p == NULL) p2 = (int*)malloc((unsigned)(size)+sizeof(int)); else p2 = (int*)realloc((char*)(p)-sizeof(int), (unsigned)(size)+sizeof(int)); if (p2 == NULL) { fprintf(stderr, "\nFailed to allocate %u bytes", (unsigned)(size)+sizeof(int)); exit(1); } *p2 = size; return p2+1; } void free2(void *p) { if (p != NULL) free(((int*)p) - 1); } void *memtraces_add(void *p, int size, int line, const char *file) { memallocated += size; if (memallocated > mempeak) mempeak = memallocated; if (p == NULL) return p; memtraces_cnt++; memtraces_var = realloc(memtraces_var, memtraces_cnt*sizeof(*memtraces_var)); memtraces_var[memtraces_cnt-1].p = p; memtraces_var[memtraces_cnt-1].size = size; memtraces_var[memtraces_cnt-1].line = line; if (strlen(file) <= sizeof(memtraces_var->file)-1) strcpy(memtraces_var[memtraces_cnt-1].file, file); else strcpy(memtraces_var[memtraces_cnt-1].file, file+strlen(file)+1-sizeof(memtraces_var->file)); if (memtraces_log != NULL) fprintf(memtraces_log, "add %p of size %d; %d allocated (line %d, source %s)\n", p, size, memallocated, line, file); return p; } void *memtraces_del(void *p, int line, const char *file) { int i; if (p == NULL) return p; memallocated -= memsize(p); for (i=0; i<memtraces_cnt; i++) if (memtraces_var[i].p == p) { memmove(memtraces_var+i, memtraces_var+i+1, (memtraces_cnt-i-1)*sizeof(*memtraces_var)); memtraces_cnt--; memtraces_var = realloc(memtraces_var, memtraces_cnt*sizeof(*memtraces_var)); if (memtraces_log != NULL) fprintf(memtraces_log, "del %p of size %d; %d allocated (line %d, source %s)\n", p, memsize(p), memallocated, line, file); return p; } fprintf(stderr, "free(%p) - wrong pointer (line %d, source %s)", p, line, file); exit(1); return p; } void memtraces(FILE *fh) { int i; fprintf(fh, "\n\nMEMORY STATUS:\n"); for (i=0; i<memtraces_cnt; i++) { fprintf(fh, "%p of size %d (line %d, source %s)\n", memtraces_var[i].p, memsize(memtraces_var[i].p), memtraces_var[i].line, memtraces_var[i].file); } fprintf(fh, "Allocated: %d; Peak: %d", memallocated, mempeak); } #ifdef NDEBUG # define realloc(p, size) realloc2(p, size) # define malloc(size) realloc2(NULL, size) # define calloc(size) ( pmemtemp=(size), memset(realloc2(NULL, pmemtemp), 0, pmemtemp) ) # define free(p) free2(p) #else # define realloc(p, size) ( pmemtemp=(size), memtraces_add(realloc2( \ memtraces_del(p, __LINE__, __FILE__), pmemtemp), pmemtemp, __LINE__, __FILE__) ) # define malloc(size) ( pmemtemp=(size), memtraces_add(realloc2( \ NULL, pmemtemp), pmemtemp, __LINE__, __FILE__) ) # define calloc(size) ( pmemtemp=(size), memset(memtraces_add(realloc2( \ NULL, pmemtemp), pmemtemp, __LINE__, __FILE__), 0, pmemtemp) ) # define free(p) free2(memtraces_del(p, __LINE__, __FILE__)) #endif #endif Чтобы получить подробный отчёт о состоянии памяти достаточно в начале программы написать #include "memtraces.h", и в конце вызвать memtraces(stderr). Как видите модуль состоит из 99 строчек кода, и основан на чистом ANSI C, поэтому будет работать на любой платформе. Недостаток - отсутствие поддержки многопоточных программ (её вы можете ввести самостоятельно путем добавления критических секций, но получится ОС-зависимо). Я не пробовал, но думаю что в C++ можно поступить аналогичным способом (там можно подменить new и delete даже не прибегая к помощи препроцессора). Вот несколько других решений утечки памяти: valgrind (все хвалят; только Линукс), dmalloc, ccmalloc, LeakTracer, YAMD. P.S. Отредактировал 2 August 2006. Исправил ошибку. |
![]() |
Сообщ.
#2
,
|
|
Такой же способ для С++.
Он не претендует на всецелосность (ибо в программе на С++ можно выделять память при помощью malloc/calloc ), а так же он, конечно, не отловит объекты, которые создавались в других библиотеках (CreateObject и т.п.). Основные идеи: Нельзя использовать стандартный коллектор (map,list и пр) так как в них самих используются new/delete. Написание своего аллокатора не помогает. (Во всяком случае, у меня не получилось =) ) Поэтому я написал небольшой класс, который добавляет/удаляет структуры с описанием выделенной памяти. У меня вся информация показывается, когда срабатывает конструктор этого класса. Так как объект класса - глобальный, он уничтожится автоматически (естественно, при корректном выходе из программы) после выполнения главной функции. А так как он, по идее, должен создаваться первым, то и уничтожаться последним. Но никто не запрещает самому вызвать mem.PrintLeaks(); Вывод осуществляется в окно output (в студии >= 6.0.) В остальных компиляторах не тестировал, не знаю, есть ли такая функция и такая возможность. Строка вывода составлена таким образом, чтобы при двойном клике на этой строка компилятор кидал вас в то место, где была выделена память. В целях оптимизации, пихаю структуры в список в начало ( ~push_front ). Идея в том, что при удалении должны, по идее, удаляться указатели по принципу FIFO. Для использования достаточно включить этот файл где-нибудь в первом файле и написать: ![]() ![]() #ifdef _DEBUG #include "ваш_файл.h" #define new DEBUG_NEW #endif ![]() ![]() #include <stdio.h> struct MemLeak { void *ptr; size_t size; const char *file; int line; }; #define INFO_LEN 300 class CMemCollect { struct Collector { MemLeak leak; Collector * next; Collector * prev; }; Collector *m_Start; void PrintStr(char * a_Str) { OutputDebugStringA( a_Str ); } void PrintInfo(MemLeak * a_pLeak) { char strInfo[INFO_LEN]; if( a_pLeak->file ) sprintf(strInfo,"%s(%d) : size = %d\n",a_pLeak->file,a_pLeak->line,a_pLeak->size); else sprintf(strInfo,"size = %d\n",a_pLeak->size); PrintStr(strInfo); } Collector * GetLast() { Collector *iter = m_Start; if( !iter ) return NULL; while ( iter->next ) { iter = iter->next; } return iter; } public: CMemCollect() { m_Start = NULL; } ~CMemCollect() { PrintLeaks(); } void PrintLeaks() { Collector *iter = GetLast(); if( iter ) PrintStr("-=MEMORY LEAK DETECTED!=-\n"); while( iter != NULL ) { MemLeak *str = &iter->leak; PrintInfo(str); Collector * prevIter = iter->prev; Remove( iter ); iter = prevIter; } } void Add(MemLeak & a_pLeak) { Collector *newStr = (Collector*)malloc(sizeof(Collector)); if( !newStr ) return; memset(newStr,0,sizeof(Collector)); memcpy(&newStr->leak,&a_pLeak,sizeof(MemLeak)); if( m_Start ) { newStr->next = m_Start; m_Start->prev = newStr; } m_Start = newStr; } void Remove(Collector *iter) { if( iter->prev ) iter->prev->next = iter->next; if( iter->next ) iter->next->prev = iter->prev; if( iter == m_Start ) m_Start = iter->next; free(iter); } void Remove(void * ptr) { Collector *iter = m_Start; while( iter != NULL ) { if( iter->leak.ptr == ptr ) { Remove(iter); return; } iter = iter->next; } } }; CMemCollect mem; void * operator new( size_t size , const char* lpszFileName, int nLine ) { void * ptr = malloc( size ); if ( !ptr ) return NULL; MemLeak leak = {0}; leak.ptr = ptr; leak.file = lpszFileName; leak.line = nLine; leak.size = size; mem.Add(leak); return ptr; } void * operator new( size_t size ) { void * ptr = malloc( size ); if ( !ptr ) return NULL; MemLeak leak = {0}; leak.size = size; leak.ptr = ptr; mem.Add(leak); return ptr; } void operator delete( void * ptr , const char* lpszFileName, int nLine ) { mem.Remove( ptr ); free( ptr ); } void operator delete(void * ptr) { mem.Remove( ptr ); free( ptr ); } #define DEBUG_NEW new(__FILE__,__LINE__) |
![]() |
Сообщ.
#3
,
|
|
Hsilgos
malloc, насколько мне известно, непотокобезовасна в отличие от new. В многопоточной программе при активном выделении памяти куча скорей всего подпортится. Предлагаю добавить класс-заглушку, которых будет взависимости от ОС и наличия многопоточности выполнять синхронизацию. Объекты синхронизации, например, в винде критическая секция, в никсах мютекс (mutex_lock(...),mutex_unlock(...) ,если память не изменяет ![]() |
![]() |
Сообщ.
#4
,
|
|
ElcnU
Объект, который будет безопасно выделять память? чёт не догнал немного =) Тогда нужно всю функцию "add" загнать в критическую секцию... Синхронизация - моя слабость. Я с многопоточностью хоть и работал, но редко где приходилось синхронизировать =( |
![]() |
Сообщ.
#5
,
|
|
Цитата Hsilgos @ Объект, который будет безопасно выделять память? чёт не догнал немного =) Тогда нужно всю функцию "add" загнать в критическую секцию... Синхронизация - моя слабость. Я с многопоточностью хоть и работал, но редко где приходилось синхронизировать =( я имел в виду написать класс обёртку для объектов синхронизации и выглядеть в конечном счёте должно примерно так ![]() ![]() static CMemCollect mem;//статик чтоб один хедер могли подключать несколько сишников static CSync MemLeakSync; void * operator new( size_t size , const char* lpszFileName, int nLine ) { MemLeakSync.Lock(); void * ptr = malloc( size ); MemLeakSync.Unlock(); if ( !ptr ) return NULL; ... MemLeakSync.Lock(); free( ptr ); MemLeakSync.Unlock(); ... что нить типа того ![]() ![]() #ifdef MEMLEAK_WIN32 #include <windows.h> #endif #ifdef MEMLEAK_NIX #include <pthread.h> #endif class CSync { private: #ifdef MEMLEAK_WIN32 CRITICAL_SECTION m_CS; #endif #ifdef MEMLEAK_NIX pthread_mutex_t m_Mutex; #endif public: CSync() { //в зависимости от продефайненых параметров производится инициализация объекта синхронизации под нужную платформу типа #ifdef MEMLEAK_WIN32 InitializeCriticalSection(&m_CS); #endif #ifdef MEMLEAK_NIX pthread_mutex_init(&m_Mutex, NULL); #endif ... } ~CSync() { #ifdef MEMLEAK_WIN32 DeleteCriticalSection(&m_CS); #endif #ifdef MEMLEAK_NIX pthread_mutex_destroy(&m_Mutex, NULL); #endif ... } void Lock() { #ifdef MEMLEAK_WIN32 EnterCriticalSection(&m_CS); #endif #ifdef MEMLEAK_NIX pthread_mutex_lock(&m_Mutex); #endif ... } void Unlock() { #ifdef MEMLEAK_WIN32 LeaveCriticalSection(&m_CS); #endif #ifdef MEMLEAK_NIX pthread_mutex_unlock(&m_Mutex); #endif ... } ... } соответственно если MEMLEAK_ХХХ не был продефайнен, то класс получиться пустой, для экономии ресурсов в однопоточный приложениях |
![]() |
Сообщ.
#6
,
|
|
Значит добавляем 2 класса =)
Небольшой шаблон для автоматического "переключения". В конструкторе вызывает первую функцию, в деструторе - вторую. Я успешно использовал его для залочивания перерисовки, пока отрабатывает метод. ![]() ![]() template <class T> class CAutoSwitcher { protected: typedef void(T::*ClassMember)(void) ; ClassMember m_MemStart; ClassMember m_MemFinish; T* m_ptClass; CAutoSwitcher(){};// no create with default construtor public: CAutoSwitcher(T* ptClass,void(T::*mem_on)(void),void(T::*mem_off)(void)) { m_MemStart = mem_on; m_MemFinish = mem_off; m_ptClass = ptClass; if(m_ptClass && m_MemStart ) ( m_ptClass->*m_MemStart )(); } virtual ~CAutoSwitcher() { if(m_ptClass && m_MemFinish ) ( m_ptClass->*m_MemFinish )(); } }; Ну и, собственно, сам класс для синхронизации (только Вынь32): ![]() ![]() class CSync { CRITICAL_SECTION m_crSect; public: CSync() { Init(); } ~CSync() { Destroy(); } void Init(); void Destroy(); void Lock(); void UnLock(); }; void CSync::Init() { InitializeCriticalSection(&m_crSect); } void CSync::Destroy() { DeleteCriticalSection(&m_crSect); } void CSync::Lock() { EnterCriticalSection(&m_crSect); } void CSync::UnLock() { LeaveCriticalSection(&m_crSect); } CSync g_Sync; #define LOCK_METHOD() CAutoSwitcher<CSync> lck(&g_Sync,&CSync::Lock,CSync::UnLock); В new/delete в самом начале пишем LOCK_METHOD() |
Сообщ.
#7
,
|
|
|
> malloc, насколько мне известно, непотокобезовасна в отличие от new.
ээ, что за глупость? во всяком случае в винде нт это сводится к HeapAlloc(), т.е. RtlAllocateHeap, которая активно юзает EnterCriticalSection и LeaveCriticalSection. А new - сводится к malloc и по сути является тем же самым - только ещё конструкторы всякие вызываются, экземплеры классов инициализируются и т.п., но память через malloc выделяется... |
![]() |
Сообщ.
#8
,
|
|
Цитата Throne @ > malloc, насколько мне известно, непотокобезовасна в отличие от new. ээ, что за глупость? во всяком случае в винде нт это сводится к HeapAlloc(), т.е. RtlAllocateHeap, которая активно юзает EnterCriticalSection и LeaveCriticalSection. А new - сводится к malloc и по сути является тем же самым - только ещё конструкторы всякие вызываются, экземплеры классов инициализируются и т.п., но память через malloc выделяется... верное замечание ![]() а вот как дела обстоят на других платформах для меня это еще под большим вопросом ![]() про new согласен, просто было малость другое убеждение:( ну по крайней мере не будет лишним синхронизовать такие вещи Цитата Hsilgos @ ![]() ![]() if( m_Start ) { newStr->next = m_Start; m_Start->prev = newStr; } |
![]() |
Сообщ.
#9
,
|
|
Цитата Это не обязательно. Стандарт разрешает даже иметь отдельные "свободные памяти" для C-функций и C++-операторов. но память через malloc выделяется... |
Сообщ.
#10
,
|
|
|
Throne,
А что вы имеете в виду под непотокобезовасными функциями? |
Сообщ.
#11
,
|
|
|
Цитата ElcnU @ malloc, насколько мне известно, непотокобезовасна в отличие от new. откуда инфа ? ![]() какая может быть потокобезопасность, если в стандарте понятия многопоточности просто нет ? |
![]() |
Сообщ.
#12
,
|
|
Цитата gena_dj @ Throne, А что вы имеете в виду под непотокобезовасными функциями? это он меня цитировал. в общих чертах отсутствие синхронизации доступа к общим участкам памяти из различных потоков Цитата C06akaDuka @ откуда инфа ? про new была статья, точно не помню откуда, вот оттуда, но насчет этого я уже отписался в 8м посту ![]() Цитата Throne @ ээ, что за глупость? во всяком случае в винде нт это сводится к HeapAlloc(), т.е. RtlAllocateHeap, которая активно юзает EnterCriticalSection и LeaveCriticalSection. А new - сводится к malloc и по сути является тем же самым - только ещё конструкторы всякие вызываются, экземплеры классов инициализируются и т.п., но память через malloc выделяется... это точно в crt исходниках от msvc. а, например, в ucLinux сборке под ADSP-BF537, нет никакой синхронизации, то есть в таких системах необходимо выполнять синхронизацию при выделении памяти, если работают несколько потоков... ЗЫ: через телнет проблематично отлавливать косяки, особенно если это проблемы с кучей(у ucLinux просто неадекватная реакция на этот счет бывает) ![]() |
Сообщ.
#13
,
|
|
|
Цитата ElcnU @ отсутствие синхронизации доступа к общим участкам памяти из различных потоков Зачем нужна синхронизация? Как она влияет на выделение памяти? Выделенная память всегда привязана к процессу, а не к потокам. Скорее всего в статье имеется в виду следующее. При использовании new после вызова _endthread память, выделенная в потоке, будет освобождена. |
Сообщ.
#14
,
|
|
|
Цитата gena_dj @ Возможна ситуация, когда один поток запросил блок памяти в процессе обработки запроса от другого потока. Это не говоря про многоядерные/многопроцессорные системы. Зачем нужна синхронизация? Как она влияет на выделение памяти? |
Сообщ.
#15
,
|
|
|
Цитата trainer @ один поток запросил блок памяти в процессе обработки запроса от другого потока. Пямять запрашивается из кучи процесса. Каждый поток имеет только свой персональный стек. Поэтому такие запросы обработаются корректно. Цитата trainer @ Это не говоря про многоядерные/многопроцессорные системы. Это ничего не изменит. |