Як використовувати valgrind для пошуку витоків пам'яті?


183

Як використовувати valgrind для пошуку витоків пам'яті в програмі?

Будь-хто, допоможіть мені та опишіть кроки щодо проведення процедури?

Я використовую Ubuntu 10.04 і у мене є програма a.c, будь ласка, допоможіть мені.


16
Ви використовуєте valgrind для тестування складеної програми, а не вихідного коду.
Тоні

6
Відповідь, подана нижче @RageD, є правильною, чому ви не приймаєте її?
Пратік Сінгал

1
Витік викликаний тим, що ви не зробите - тобто. безкоштовно виділена пам'ять. Отже, Вальгринд не може показати вам "де" витік - тільки ви знаєте, де виділена пам'ять більше не потрібна. Однак, повідомляючи, яке виділення не є вільним () d, відслідковуючи використання цієї пам'яті через вашу програму, ви повинні мати можливість визначити, де вона має отримати безкоштовно () d. Поширена помилка - помилка виходу з функції без звільнення виділеної пам'яті.
MikeW

1
Пов'язаний: з будь-яким інструментом: stackoverflow.com/questions/6261201 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

Відповіді:


297

Як запустити Valgrind

Не ображайте ОП, але для тих, хто приходить до цього питання і все ще є новим для 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)

Давайте подивимось на код С, про який я теж писав:

#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

Тепер при цій збірці налагодження Вальгрінд вказує на точний рядок коду, що виділяє пам'ять, яка просочилася! (Формулювання важливе: це може бути не саме місце вашого протікання, а те, що просочилося. Слід допоможе вам знайти де .)

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;
}

Зауважте, що Вальгрінд вказує на коментований рядок коду вище. Масив розміром 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;
}

Вальгрінд вказує на коментований рядок вище. Подивіться останню ітерацію тут, яка є
*(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 Янссон. Янссон відслідковує вашу пам’ять для вас, і вам просто потрібно, щоб 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? Що насправді є витоком пам’яті та помилкою пам’яті? Чим вони відрізняються від segfault? Як працює Valgrind? Якщо у вас було щось із цього, будь-ласка, подайте цікавість:


4
Набагато краща відповідь, прикро це не прийнята відповідь.
А. Смоляк

Я вважаю, що добре робити таку справу, я зробив декілька сам
А. Смоляк

1
Чи можу я визначити цю відповідь зіркою і використати її як майбутню орієнтир для себе? Гарна робота!
Зап

чи memcheckувімкнено інструмент за замовчуванням?
abhiarora

@abhiarora Так. Сторінка людини повідомляє нам, що memcheckце інструмент за замовчуванням:--tool=<toolname> [default: memcheck]
Джошуа Детвейлер

146

Спробуйте це:

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

Поки valgrind встановлений, він перейде через вашу програму і підкаже, що не так. Це може дати вам вказівки та приблизні місця, де можуть бути знайдені ваші витоки. Якщо ви працюєте по умолчанню, спробуйте запустити його 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.