Як використовувати 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)
Давайте подивимось на код С, про який я теж писав:
#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)
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;
}
Зауважте, що Вальгрінд вказує на коментований рядок коду вище. Масив розміром 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)
знову поза межами, як і недійсне записування. Недійсні читання також є загальним результатом помилок, що не стосуються один одного. Подивіться на праву частину операції по призначенню.
Як дізнатись, коли витік мій? Як я можу знайти витік, коли використовую чужий код? Я знайшов витік, який не мій; я повинен щось робити? Усі законні питання. По-перше, 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 Янссон. Янссон відслідковує вашу пам’ять для вас, і вам просто потрібно, щоб 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? Що насправді є витоком пам’яті та помилкою пам’яті? Чим вони відрізняються від segfault? Як працює Valgrind? Якщо у вас було щось із цього, будь-ласка, подайте цікавість:
memcheck
увімкнено інструмент за замовчуванням?
memcheck
це інструмент за замовчуванням:--tool=<toolname> [default: memcheck]
Спробуйте це:
valgrind --leak-check=full -v ./your_program
Поки valgrind встановлений, він перейде через вашу програму і підкаже, що не так. Це може дати вам вказівки та приблизні місця, де можуть бути знайдені ваші витоки. Якщо ви працюєте по умолчанню, спробуйте запустити його 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 у поточному каталозі.