Как использовать valgrind для обнаружения утечек памяти в программе?
Пожалуйста, кто-нибудь, помогите мне и опишите шаги для проведения процедуры?
Я использую Ubuntu 10.04 и у меня есть программа a.c
, пожалуйста, помогите мне.
Как использовать valgrind для обнаружения утечек памяти в программе?
Пожалуйста, кто-нибудь, помогите мне и опишите шаги для проведения процедуры?
Я использую Ubuntu 10.04 и у меня есть программа a.c
, пожалуйста, помогите мне.
Ответы:
Не для того, чтобы оскорблять ОП, но для тех, кто пришел к этому вопросу и все еще не знаком с Linux - вам, возможно, придется установить Valgrind в вашей системе.
sudo apt install valgrind # Ubuntu, Debian, etc.
sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
Valgrind легко использовать для кода C / C ++, но при правильной настройке его можно использовать и для других языков (см. Это для Python).
Чтобы запустить Valgrind , передайте исполняемый файл в качестве аргумента (вместе с любыми параметрами в программу).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
Короче говоря, флаги:
--leak-check=full
: "каждая отдельная утечка будет показана подробно"--show-leak-kinds=all
: Показать все «определенные, косвенные, возможные, достижимые» виды утечек в «полном» отчете.--track-origins=yes
: Воспользуйтесь полезным выводом на скорости. Это отслеживает происхождение неинициализированных значений, которые могут быть очень полезны для ошибок памяти. Подумайте об отключении, если Valgrind неприемлемо медленен.--verbose
: Может рассказать вам о необычном поведении вашей программы. Повторите для большего многословия.--log-file
: Запись в файл Полезно, когда вывод превышает пространство терминала.Наконец, вы хотели бы увидеть отчет Valgrind, который выглядит следующим образом:
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Итак, у вас утечка памяти, и Вальгринд не говорит ничего значимого. Возможно, что-то вроде этого:
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (in /home/Peri461/Documents/executable)
Давайте посмотрим на код C, который я тоже написал:
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
Ну, было потеряно 5 байтов. Как это произошло? Сообщение об ошибке просто говорит
main
и malloc
. В более крупной программе это было бы очень сложно выследить. Это из-за того, как исполняемый файл был скомпилирован . На самом деле мы можем получить построчную информацию о том, что пошло не так. Перекомпилируйте вашу программу с флагом отладки (я использую gcc
здесь):
gcc -o executable -std=c11 -Wall main.c # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c # add -ggdb3 to it
Теперь с этой отладочной сборкой Valgrind указывает на точную строку кода, выделяющую утечку памяти! (Формулировка важна: это может быть не совсем то, где находится ваша утечка, а то , что просочилось. След поможет вам найти, где .)
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (main.c:4)
IndexOutOfBoundsException
проблемы с типом.Иногда ваши утечки / ошибки могут быть связаны друг с другом, подобно тому, как IDE обнаруживает, что вы еще не ввели закрывающую скобку. Решение одной проблемы может решить другие, поэтому найдите такую, которая выглядит хорошим виновником, и примените некоторые из этих идей:
gdb
возможно) и ищите ошибки предусловия / постусловия. Идея состоит в том, чтобы отслеживать выполнение вашей программы, сосредотачиваясь на времени жизни выделенной памяти.60 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
by 0x4005E4: resizeArray (main.c:12)
by 0x40062E: main (main.c:19)
И код:
#include <stdlib.h>
#include <stdint.h>
struct _List {
int32_t* data;
int32_t length;
};
typedef struct _List List;
List* resizeArray(List* array) {
int32_t* dPtr = array->data;
dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
return array;
}
int main() {
List* array = calloc(1, sizeof(List));
array->data = calloc(10, sizeof(int32_t));
array = resizeArray(array);
free(array->data);
free(array);
return 0;
}
Как помощник преподавателя, я часто видел эту ошибку. Студент использует локальную переменную и забывает обновить исходный указатель. Ошибка здесь замечает, что realloc
фактически может переместить выделенную память куда-нибудь еще и изменить местоположение указателя. Затем мы уходим, resizeArray
не сообщая,
array->data
куда был перемещен массив.
1 errors in context 1 of 1:
Invalid write of size 1
at 0x4005CA: main (main.c:10)
Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
at 0x4C2B975: calloc (vg_replace_malloc.c:711)
by 0x400593: main (main.c:5)
И код:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* alphabet = calloc(26, sizeof(char));
for(uint8_t i = 0; i < 26; i++) {
*(alphabet + i) = 'A' + i;
}
*(alphabet + 26) = '\0'; //null-terminate the string?
free(alphabet);
return 0;
}
Обратите внимание, что Valgrind указывает нам на закомментированную строку кода выше. Массив размером 26 индексируется [0,25], поэтому *(alphabet + 26)
является недопустимой записью - это выходит за пределы. Недопустимая запись является распространенным результатом ошибок «один за другим». Посмотрите на левую сторону вашей операции присваивания.
1 errors in context 1 of 1:
Invalid read of size 1
at 0x400602: main (main.c:9)
Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x4005E1: main (main.c:6)
И код:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* destination = calloc(27, sizeof(char));
char* source = malloc(26 * sizeof(char));
for(uint8_t i = 0; i < 27; i++) {
*(destination + i) = *(source + i); //Look at the last iteration.
}
free(destination);
free(source);
return 0;
}
Valgrind указывает нам на прокомментированную строку выше. Посмотрите на последнюю итерацию здесь, которая есть
*(destination + 26) = *(source + 26);
. Тем не менее, *(source + 26)
снова выходит за пределы, так же, как и недопустимая запись. Недопустимые чтения также являются частым результатом ошибочных ошибок. Посмотрите на правую сторону вашей операции присваивания.
Как я узнаю, когда утечка моя? Как мне найти утечку, когда я использую чужой код? Я нашел утечку, которая не моя; я должен что-то сделать? Все законные вопросы. Во-первых, 2 реальных примера, которые показывают 2 класса общих встреч.
#include <jansson.h>
#include <stdio.h>
int main() {
char* string = "{ \"key\": \"value\" }";
json_error_t error;
json_t* root = json_loads(string, 0, &error); //obtaining a pointer
json_t* value = json_object_get(root, "key"); //obtaining a pointer
printf("\"%s\" is the value field.\n", json_string_value(value)); //use value
json_decref(value); //Do I free this pointer?
json_decref(root); //What about this one? Does the order matter?
return 0;
}
Это простая программа: она читает строку JSON и анализирует ее. При создании мы используем библиотечные вызовы, чтобы выполнить анализ для нас. Янссон динамически распределяет необходимые распределения, поскольку JSON может содержать собственные вложенные структуры. Однако это не означает, что мы decref
или «освобождаем» память, данную нам от каждой функции. На самом деле, этот код, который я написал выше, выбрасывает «Неверное чтение» и «Неверное чтение». Эти ошибки исчезают, когда вы убираете decref
строку для value
.
Зачем? Переменная value
считается «заимствованной ссылкой» в API Jansson. Янссон следит за своей памятью для вас, и вам просто нужно, чтобы decref
структуры JSON были независимы друг от друга. Урок здесь:
прочитайте документацию . В самом деле. Иногда это трудно понять, но они говорят вам, почему это происходит. Вместо этого у нас есть
существующие вопросы об этой ошибке памяти.
#include "SDL2/SDL.h"
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Quit();
return 0;
}
Что не так с этим кодом ? Это постоянно пропускает ~ 212 КиБ памяти для меня. Найдите минутку, чтобы подумать об этом. Мы включаем SDL и затем выключаем. Ответ? Нет ничего плохого.
Поначалу это может показаться странным . По правде говоря, графика грязная, и иногда вы должны принять некоторые утечки как часть стандартной библиотеки. Урок здесь: вам не нужно подавлять каждую утечку памяти . Иногда вам просто нужно подавить утечки, потому что это известные проблемы, с которыми вы ничего не можете сделать . (Это не мое разрешение игнорировать ваши собственные утечки!)
Как я узнаю, когда утечка моя?
Это. (99% уверен, в любом случае)
Как мне найти утечку, когда я использую чужой код?
Скорее всего, кто-то другой уже нашел это. Попробуйте Google! Если это не поможет, используйте навыки, которые я дал вам выше. Если это не помогло, и вы в основном видите вызовы API и немного собственной трассировки стека, см. Следующий вопрос.
Я нашел утечку, которая не моя; я должен что-то сделать?
Да! У большинства API есть способы сообщать об ошибках и проблемах. Используй их! Помогите вернуть инструменты, которые вы используете в своем проекте!
Спасибо, что остался со мной так долго. Я надеюсь, что вы чему-то научились, так как я пытался склониться к широкому кругу людей, приходящих к этому ответу. Надеюсь, вы спросили кое-что еще: как работает распределитель памяти в C? Что такое утечка памяти и ошибка памяти? Чем они отличаются от сегфо? Как работает Valgrind? Если у вас есть что-то из этого, пожалуйста, подпитывайте свое любопытство:
memcheck
инструмент включен по умолчанию?
memcheck
это инструмент по умолчанию:--tool=<toolname> [default: memcheck]
Попробуй это:
valgrind --leak-check=full -v ./your_program
Пока установлен valgrind, он будет проходить через вашу программу и сообщать вам, что не так. Это может дать вам указатели и приблизительные места, где ваши утечки могут быть найдены. Если вы segfault'ing, попробуйте запустить его gdb
.
your_program
== имя исполняемого файла или любую другую команду, которую вы используете для запуска приложения.
Вы можете запустить:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Вы можете создать псевдоним в файле .bashrc следующим образом
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
Поэтому, когда вы хотите проверить утечки памяти, просто
vg ./<name of your executable> <command line parameters to your executable>
Это создаст файл журнала Valgrind в текущем каталоге.