Как мне использовать valgrind для поиска утечек памяти?


183

Как использовать valgrind для обнаружения утечек памяти в программе?

Пожалуйста, кто-нибудь, помогите мне и опишите шаги для проведения процедуры?

Я использую Ubuntu 10.04 и у меня есть программа a.c, пожалуйста, помогите мне.


16
Вы используете valgrind для тестирования вашей скомпилированной программы, а не исходного кода.
Тони

6
Ответ @RageD, приведенный ниже, правильный, почему вы его не принимаете?
Пратик Сингхал

1
Утечка вызвана чем-то, что вы не можете сделать - т.е. свободная выделенная память. Следовательно, Valgrind не может показать вам «где» утечка - только вы знаете, где выделенная память больше не требуется. Однако, сообщая вам, какое распределение не является свободным () d, отслеживая использование этой памяти через вашу программу, вы должны быть в состоянии определить, где она должна освободиться () d. Распространенной ошибкой является выход из функции без освобождения выделенной памяти.
MikeW

Ответы:


299

Как запустить Вальгринд

Не для того, чтобы оскорблять ОП, но для тех, кто пришел к этому вопросу и все еще не знаком с 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)

Методы отладки утечек памяти и ошибок

  • Воспользуйтесь www.cplusplus.com ! Он имеет отличную документацию по функциям C / C ++.
  • Общие советы по утечке памяти:
    • Убедитесь, что ваша динамически выделенная память действительно освобождается.
    • Не выделяйте память и забудьте назначить указатель.
    • Не перезаписывайте указатель новым, если старая память не освобождена.
  • Общие советы по ошибкам памяти:
    • Получите доступ и напишите адреса и индексы, которые, как вы уверены, принадлежат вам. Ошибки памяти отличаются от утечек; они часто просто IndexOutOfBoundsException проблемы с типом.
    • Не открывайте и не пишите в память после освобождения.
  • Иногда ваши утечки / ошибки могут быть связаны друг с другом, подобно тому, как IDE обнаруживает, что вы еще не ввели закрывающую скобку. Решение одной проблемы может решить другие, поэтому найдите такую, которая выглядит хорошим виновником, и примените некоторые из этих идей:

    • Перечислите функции в вашем коде, которые зависят от / зависят от «нарушающего» кода, который имеет ошибку памяти. Следите за выполнением программы (возможно, даже gdbвозможно) и ищите ошибки предусловия / постусловия. Идея состоит в том, чтобы отслеживать выполнение вашей программы, сосредотачиваясь на времени жизни выделенной памяти.
    • Попробуйте закомментировать «нарушающий» блок кода (в пределах разумного, чтобы ваш код все еще компилировался). Если ошибка Valgrind исчезнет, ​​вы найдете ее.
  • Если ничего не помогает, попробуйте найти его. У Valgrind тоже есть документация !

Взгляд на общие утечки и ошибки

Следите за своими указателями

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)снова выходит за пределы, так же, как и недопустимая запись. Недопустимые чтения также являются частым результатом ошибочных ошибок. Посмотрите на правую сторону вашей операции присваивания.


Топия с открытым исходным кодом (U / Dys)

Как я узнаю, когда утечка моя? Как мне найти утечку, когда я использую чужой код? Я нашел утечку, которая не моя; я должен что-то сделать? Все законные вопросы. Во-первых, 2 реальных примера, которые показывают 2 класса общих встреч.

Янссон : библиотека JSON

#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 были независимы друг от друга. Урок здесь: прочитайте документацию . В самом деле. Иногда это трудно понять, но они говорят вам, почему это происходит. Вместо этого у нас есть существующие вопросы об этой ошибке памяти.

SDL : библиотека графики и игр

#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? Если у вас есть что-то из этого, пожалуйста, подпитывайте свое любопытство:


4
Гораздо лучший ответ, позор, это не принятый ответ.
Смоляк А.

Я полагаю, что это хорошая практика, я делал это сам
А. Смоляк,

1
Могу ли я отметить этот ответ и использовать его в качестве справочной информации для себя? Хорошая работа!
Зап

это memcheckинструмент включен по умолчанию?
abhiarora

@abhiarora Да. Страница man говорит нам, что memcheckэто инструмент по умолчанию:--tool=<toolname> [default: memcheck]
Джошуа Детвилер

146

Попробуй это:

valgrind --leak-check=full -v ./your_program

Пока установлен valgrind, он будет проходить через вашу программу и сообщать вам, что не так. Это может дать вам указатели и приблизительные места, где ваши утечки могут быть найдены. Если вы segfault'ing, попробуйте запустить его gdb.


Что означает "ваша_программа"? Это расположение исходного кода или имя приложения, например файл apk?
HoangVu

7
your_program== имя исполняемого файла или любую другую команду, которую вы используете для запуска приложения.
RageD


1

Вы можете создать псевдоним в файле .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 в текущем каталоге.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.