|
Программирование >> Разработка устойчивых систем
Спасибо Pery Чарни (Reg Chamey) из Комитета по стандартизации С++, предложившему этот прием. Чтобы использовать систему проверки памяти, включите заголовочный файл MemCheck.h, скомпонуйте файл MemCheck.obj со своим приложением для перехвата всех вызовов new и delete и активизируйте трассировку памяти макросом MEM ON() (см. далее). Информация обо всех операциях выделения и освобождения памяти будет направляться в стандартный выходной поток (через stdout). При использовании этой системы для всех вызовов new сохраняется имя файла и номер строки, содержащей вызов. Для этого мы задействуем синтаксис размещения оператора new. Хотя обычно синтаксис размещения применяется в тех случаях, когда объекты должны находиться по определенному адресу памяти, он также позволяет создать функцию operator new() с любым количеством аргументов. В следующем примере дополнительные аргументы используются для сохранения результатов макросов FILE и LINE при каждом вызове new: : С02:MemCheck.h #1fndef МЕМСНЕСК Н Idefine МЕМСНЕСК Н #1nclude <cstddef> для size t Перехват оператора new (в версиях для скаляров и массивов) void* operator newCstd::size t. const char*, long): void* operator new[](std::size t. const char*, long): , #define new new ( FILE . LINE ) extern bool traceFlag: #define TRACE ON() traceFlag = true #define TRACE OFF() traceFlag = false extern bool activeFlag: #define MEM ON() activeFlag - true #define MEM OFF() activeFlag = false #endif MEMCHECK H /:- Этот файл должен включаться во все исходные файлы, для которых требуется отслеживать операции с кучей, но включаться он должен обязательно последним (после всех остальных директив #include). Большинство заголовочных файлов стандартной библиотеки содержит шаблоны, а поскольку многие компиляторы используют при компиляции шаблонов модель с включением, макрос, заменяющий new в MemCheck.h, узурпирует все вхождения оператора new в исходном коде библиотеки (что, скорее всего, приведет к ошибкам компиляции). Кроме того, нас интересуют наши собственные ошибки в нашем коде, а не в библиотеках. В следующем файле, содержащем реализацию системы отслеживания операций с памятью, весь ввод-вывод осуществляется стандартными средствами С (вместо потоков С++). В принципе, это должно быть несущественно, поскольку мы не мешаем потокам ввода-вывода работать с кучей, но некоторые компиляторы были против. С другой стороны, версия <cstdio> никаких нареканий не вызывала. : C02:MemCheck.cpp {0} #include <cstdio> #i nclude <cstdlib> #include <cassert> #i nclude <cstddef> using namespace std: Статический фиктивный объект Sentinel s; } Конец анонимного пространства имен Перегрузка скалярной версии new void* operator new(size t siz. const char* file, long line) void* p = malloc(siz); if (activePlag) { #undef new Глобальные флаги, устанавливаемые макросами MemCheck.h bool traceFlag = true; bool activePlag = false: namespace { Тип записи в карте памяти struct Info { void* ptr: const char* file: long line; Данные карты памяти const size t MAXPTRS = lOOOOu; Info memMap[MAXPTRS]; size t nptrs = 0; Поиск адреса в карте int findPtr(void* p) { for (int i =0: i < nptrs: ++i) if (memMap[i].ptr == p) return i; return -1; void delPtrCvoid* p) { int pos = findPtr(p): assert(p >= 0): Удаление указателя из карты for (size t i = pos: i < nptrs-l; ++i) memMap[i] = memMap[i+l]: --nptrs; Фиктивный тип для статического деструктора struct Sentinel { -Sentinel О { if (nptrs > 0) { printf( Leaked memory at:\n ): for (size t i = 0: i < nptrs: ++i) pnntf( \t3:p (file: s, line ld)\n . memMap[i].ptr. memMap[i].file. memMap[i].line): else printf( No user memory leaks!\n ): if (nptrs == MAXPTRS) { pr1ntf( memory map too small (increase MAXPTRS)\n ): exit(l): memMap[nptrs].ptr = p: memMap[nptrs].file = file; memMap[nptrs].line = line; ++nptrs; if (traceFlag) ( printf( Allocated bytes at address p . siz. p); printf( (file: s. line: ld)\n . file, line): return p: Перегрузка версии new для массивов void* operator new[](size t siz. const char* file, long line) { return operator new(siz. file, line): Перегрузка скалярной версии delete void operator delete(void* p) { if (findPtr(p) >= 0) ( free(p): assert(nptrs > 0): delPtr(p); if (traceFlag) printf( Deleted memory at address p\n . p); else if (!p && activeFlag) printf( Attempt to delete unknown pointer: p\n . p): Перегрузка версии delete для массива void operator delete[](void* p) { operator delete(p): } III:- Логические флаги traceFlag и activeFlag являются глобальными, поэтому их состояние может изменяться в программе макросами TRACE ON(),TRACE OFF(), MEM ON() и MEM OFF(). Обычно в пару макросов MEM ON()-MEM OFF() заключается весь код main(), чтобы операции с памятью отслеживались всегда. Трассировка, выводящая информацию о работе заменителей operator new() и operator delete(), включена по умолчанию, но ее можно отключить макросом TRACE OFF(). В любом случае итоговый результат выводится всегда (см. примеры тестов далее в этой главе). Система MemCheck отслеживает операции с памятью, для чего все адреса, выделенные оператором new, сохраняются в массиве структур Info. В массиве также хранятся имена файлов и номера строк, в которых находился вызов new. Чтобы предотвратить конфликты с именами глобального пространства имен, мы стараемся сохранить как можно больше информации в анонимном пространстве. Класс Sentinel существует только для вызова деструктора статического объекта при за-верщении программы. Деструктор просматривает тетМар и смотрит, остались ли в карте памяти неудаленные указатели (что является признаком утечки памяти). Наша функция operator new() получает память с помощью функции malloc(), после чего включает указатель и связанные с ним данные в тетМар. Функция operator delete() отменяет эти операции, вызывая free() и уменьшая nptrs, но сначала она проверяет.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |