c: стратегии отладки неясных утечек памяти?
Я работаю над проектом в c, и я пытаюсь понять, как отладить неясную ошибку, которая приводит к сбою моей программы. Это довольно большое, попытки изолировать проблему, делая меньшие версии кода, не работают. Поэтому я пытаюсь найти способ отладки и обнаружения утечки памяти.
Я придумал следующий план: я знаю, что проблема заключается в запуске определенной функции, и эта функция вызывает себя рекурсивно. Поэтому я подумал, что смогу сделать снимок своего рода распределения памяти программ. Так как я не знаю, Джек, что происходит под капотом (я знаю немного недостаточно, чтобы быть полезным в этой ситуации):
typedef struct record_mem {
int num_allocs;
int num_frees;
int size_space;
int num_structure_1;
...
int num_structure_N;
int num_records;
struct record_mem *next;
} RECORD;
extern RECORD *top;
void pushmem(RECORD **top)
{
RECORD *nnew = 0;
RECORD *nnew = (RECORD *)malloc(sizeof(RECORD));
nnew->num_allocs=1;
nnew->num_frees=0;
nnew->size_space=sizeof(RECORD);
nnew->num_structure_1=0;
...
nnew->num_structure_N=0;
nnew->num_records=1;
nnew->next=0;
if(*top)
{
nnew->num_allocs+=(*top)->num_allocs;
nnew->num_frees=(*top)->num_frees;
nnew->size_space+=(*top)->size_space;
nnew->num_structure_1=(*top)->num_allocs;
...
nnew->num_structure_N=(*top)->num_allocs;
nnew->num_records+=(*top)->num_records;
nnew->next=*top;
}
*top=nnew;
}
идея заключается в том, что я распечатываю содержимое своей памяти, хранящейся до момента сбоя моей программы (я знаю, где она падает, благодаря GDB).
и затем во всей программе (для каждой структуры данных в моей программе у меня есть аналогичная функция push, как описано выше), я могу просто добавить один вкладыш с функцией, подсчитывающей распределение структуры данных плюс общее выделение памяти в стеке (куче?) (которое я могу отслеживать из). Я просто создаю больше структур memory_record везде, где я чувствую необходимость записать моментальный снимок моей программы. Проблема в том, что эта запись баланса памяти не поможет, если я не могу каким-то образом записать, сколько памяти фактически используется.
Но как бы я это сделал? Кроме того, как бы я учел висячие указатели и утечки? Я использую OS X, и в настоящее время я ищу, как я мог бы записать указатель стека и другие вещи.
РЕДАКТИРОВАТЬ: Так как вы спросили: выходные данные valgrind: (closure() - это функция, вызываемая из main, которая возвращает неверный указатель: она должна возвращать заголовок двусвязного списка, traversehashmap() - это функция, вызываемая из closure () Я использую для вычисления и добавления дополнительного узла в связанный список, который вызывает себя рекурсивно, потому что ему нужно перемещаться между узлами.)
jason-danckss-macbook:project Jason$ valgrind --leak-check=full --tool=memcheck ./testc
Will attempt to compute closure of AB:
Result: testcl: 0x10000d0b0
==7682== Invalid read of size 8
==7682== at 0x100001D4E: printrelation2 (relation.h:490)
==7682== by 0x100003CFE: main (test-computation.c:47)
==7682== Address 0x10000cee8 is 8 bytes inside a block of size 24 free'd
==7682== at 0xD828: free (vg_replace_malloc.c:450)
==7682== by 0x100001232: destroyrelation2 (relation.h:161)
==7682== by 0x100003407: destroyallhashmap (computation.h:333)
==7682== by 0x1000039E1: closure (computation.h:539)
==7682== by 0x100003CBE: main (test-computation.c:38)
==7682==
==7682==
==7682== HEAP SUMMARY:
==7682== in use at exit: 5,360 bytes in 48 blocks
==7682== total heap usage: 99 allocs, 51 frees, 6,640 bytes allocated
==7682==
==7682== 48 (24 direct, 24 indirect) bytes in 1 blocks are definitely lost in loss record 33 of 37
==7682== at 0xC283: malloc (vg_replace_malloc.c:274)
==7682== by 0x100001104: getnewrelation (relation.h:134)
==7682== by 0x100001848: copyrelation (relation.h:343)
==7682== by 0x100003991: closure (computation.h:531)
==7682== by 0x100003CBE: main (test-computation.c:38)
==7682==
==7682== 1,128 (24 direct, 1,104 indirect) bytes in 1 blocks are definitely lost in loss record 36 of 37
==7682== at 0xC283: malloc (vg_replace_malloc.c:274)
==7682== by 0x100002315: getnewholder (dependency.h:129)
==7682== by 0x100003B17: main (test-computation.c:14)
==7682==
==7682== LEAK SUMMARY:
==7682== definitely lost: 48 bytes in 2 blocks
==7682== indirectly lost: 1,128 bytes in 44 blocks
==7682== possibly lost: 0 bytes in 0 blocks
==7682== still reachable: 4,096 bytes in 1 blocks
==7682== suppressed: 88 bytes in 1 blocks
==7682== Reachable blocks (those to which a pointer was found) are not shown.
==7682== To see them, rerun with: --leak-check=full --show-reachable=yes
==7682==
==7682== For counts of detected and suppressed errors, rerun with: -v
==7682== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
3 ответа
От твоего valgrind
выход:
Это, вероятно, то, что вызывает вашу проблему:
==7682== Invalid read of size 8
==7682== at 0x100001D4E: printrelation2 (relation.h:490)
==7682== by 0x100003CFE: main (test-computation.c:47)
==7682== Address 0x10000cee8 is 8 bytes inside a block of size 24 free'd
==7682== at 0xD828: free (vg_replace_malloc.c:450)
==7682== by 0x100001232: destroyrelation2 (relation.h:161)
==7682== by 0x100003407: destroyallhashmap (computation.h:333)
==7682== by 0x1000039E1: closure (computation.h:539)
==7682== by 0x100003CBE: main (test-computation.c:38)
Давайте углубимся
==7682== Invalid read of size 8
==7682== at 0x100001D4E: printrelation2 (relation.h:490)
==7682== by 0x100003CFE: main (test-computation.c:47)
Это краткое изложение вашей ошибки. Вы получаете доступ к нераспределенной (или ранее выделенной, а затем освобожденной) ячейке памяти размером 8 байтов в printrelation2
в строке 490 отношения.h.
==7682== Address 0x10000cee8 is 8 bytes inside a block of size 24 free'd
Доступный адрес имеет длину 8 байтов внутри блока размером 24, то есть, вероятно, поле размером 8 байтов в структуре размера 24 (ищите такую структуру), если вы ранее освободили этот адрес.
==7682== at 0xD828: free (vg_replace_malloc.c:450)
==7682== by 0x100001232: destroyrelation2 (relation.h:161)
==7682== by 0x100003407: destroyallhashmap (computation.h:333)
==7682== by 0x1000039E1: closure (computation.h:539)
==7682== by 0x100003CBE: main (test-computation.c:38)
Это стек вызовов, которые привели к освобождению адреса, на который вы ссылались в момент сбоя вашей программы. Это начинается со свободного, что нормально, потому что вы, вероятно, использовали free
функция для освобождения памяти. Однако файл и строка являются стандартной библиотекой, поэтому не очень актуальны. Что важно, хотя это то, что это бесплатный называется destroyrelation2
в строке 161 в отношении.h, и это неисправный свободный. destroyrelation2
сам называется destroyallhashmap
который называется closure
который называется main
в строке 38 test-computation.c. Вам нужно выяснить, какая ошибка в ваших распределениях вызывает повторное использование указателя в printrelation2, который вы ранее освободили в main в строке 38.
Утечка памяти, о которой сообщают впоследствии, существует, но маловероятно, что вызывает Ваш сбой.
Вывод valgrind яснее?
Примечание 1: Этот отчет об утечках памяти может измениться после того, как вы исправите свой segfault, но, как и сейчас, вот как я его интерпретирую:
==7682== 48 (24 direct, 24 indirect) bytes in 1 blocks are definitely lost in loss record 33 of 37
==7682== at 0xC283: malloc (vg_replace_malloc.c:274)
==7682== by 0x100001104: getnewrelation (relation.h:134)
==7682== by 0x100001848: copyrelation (relation.h:343)
==7682== by 0x100003991: closure (computation.h:531)
==7682== by 0x100003CBE: main (test-computation.c:38)
==7682==
==7682== 1,128 (24 direct, 1,104 indirect) bytes in 1 blocks are definitely lost in loss record 36 of 37
==7682== at 0xC283: malloc (vg_replace_malloc.c:274)
==7682== by 0x100002315: getnewholder (dependency.h:129)
==7682== by 0x100003B17: main (test-computation.c:14)
==7682==
==7682== LEAK SUMMARY:
==7682== definitely lost: 48 bytes in 2 blocks
==7682== indirectly lost: 1,128 bytes in 44 blocks
==7682== possibly lost: 0 bytes in 0 blocks
==7682== still reachable: 4,096 bytes in 1 blocks
==7682== suppressed: 88 bytes in 1 blocks
Начнем с краткого изложения:
==7682== LEAK SUMMARY:
==7682== definitely lost: 48 bytes in 2 blocks
==7682== indirectly lost: 1,128 bytes in 44 blocks
==7682== possibly lost: 0 bytes in 0 blocks
==7682== still reachable: 4,096 bytes in 1 blocks
==7682== suppressed: 88 bytes in 1 blocks
У вас есть два блока выделенной памяти, которые не доступны через указатели. Это означает, что где-то в программе вы размещаете их неправильно, а в какой-то момент вы полностью забываете о них. Это плохие утечки памяти. Вам необходимо пересмотреть свою логику, чтобы сохранить контроль над этими блоками или освободить их раньше в жизни программы. Я не уверен в косвенной потере, я бы сказал, что у вас нет прямых дескрипторов для ваших блоков, но у вас есть указатели на структуры, которые имеют дескрипторы для блоков. Эти утечки памяти могут быть уменьшены путем освобождения указателей в структурах перед выходом. Я не знаю о "возможно потерянном" и никогда не имел с Valgrind. "все еще достижимы" - это хорошие утечки памяти, то есть в момент сбоя valgrind вы не освободили все еще достижимый блок, но у вас есть указатель на него, и вы можете легко добавить вызов, чтобы освободить этот указатель и устранить утечку памяти.
Два стека вызовов показывают malloc, которые приводят к утечкам памяти, за вычетом "все еще достижимых" утечек (чтобы увидеть их, вы должны добавить опции --leak-check-full --show-reachable=yes
к вашему призыву Valgrind.
Примечание 2: избегайте имен функций, таких как destroyallhashmap (трудно читаемый) или destroyrelation2 (пронумеровано). Предпочитайте destroy_all_hashmap или менее обычный (в C) destroyAllHashmap и избегайте нумерации ваших функций. Точно так же избегайте имен переменных, таких как nnew, но используйте семантически чувствительные имена переменных.
Вы пробовали valgrind (и его memcheck)?
$ valgrind --tool=memcheck --leak-check=full ./yourprogram
(и желательно скомпилировать вашу программу с -g
)
Редактировать: извините, я не читал, что вы не хотите использовать Valgrind, но, как указано в комментарии dureuill к вашему сообщению, это очень полезно, и изучение того стоит.
Другая информация: утечка памяти вызвана отсутствием free
после некоторого malloc
или же realloc
(Вы можете увидеть здесь простой пример в C). Вы также можете использовать grep
(с -n
чтобы получить линию и -r
для рекурсивного поиска) перечислить все строки выделения памяти в вашей программе; и попытаться сопоставить каждого из них с призывом free
, Однако, это может быть утомительно, и я искренне верю, что использование Valgrind будет быстрее.
Поскольку я видел Valgrind во всех предложениях, я бы порекомендовал несколько других более общих, которые оказались полезными с течением времени.
Сузьте код, чтобы найти ошибку
Во-первых, труднее использовать любой инструмент / отслеживать большую систему. Попробуйте сузить вопрос.
Например, отключите модули (закомментируйте фрагменты кода и посмотрите, продолжаете ли вы выпускать проблему). Некоторые хиты и испытания должны заставить вас устранить большую часть вашего кода, если только это не будет серьезным случайным повреждением памяти.
Удалить динамическую память или, по крайней мере, закомментировать выделение памяти
Попробуйте комментировать вызовы "без памяти" (если вы можете избежать переполнения системной памяти). Таким образом, вы можете, по крайней мере, устранить или сузить проблемы с deallocs. А еще лучше попробуйте запустить всю систему, используя статически распределенную память. Я знаю, что это может быть не очень практично, но как только у вас будет ограниченная область, вызывающая сбой, вы сможете выделить статически достаточно большой объем памяти вместо динамических. Может быть создать массив узлов и назначить их указателям.
Звоните в стек и смотрите на место аварии
Я предполагаю, что вы уже проверили свой стек вызовов в момент сбоя и проверили локально доступные указатели. Это должен был быть очень немедленный подход, прежде чем пытаться что-либо из вышеперечисленного.