Jak używać valgrind, aby znaleźć wycieki pamięci w programie?
Proszę, niech mi ktoś pomoże i opisz kroki, aby przeprowadzić procedurę?
Używam Ubuntu 10.04 i mam program a.c
, pomóż mi.
Jak używać valgrind, aby znaleźć wycieki pamięci w programie?
Proszę, niech mi ktoś pomoże i opisz kroki, aby przeprowadzić procedurę?
Używam Ubuntu 10.04 i mam program a.c
, pomóż mi.
Odpowiedzi:
Nie po to, aby obrażać OP, ale dla tych, którzy przychodzą do tego pytania i wciąż są nowicjuszami w Linuksie - być może będziesz musiał zainstalować Valgrind w swoim systemie.
sudo apt install valgrind # Ubuntu, Debian, etc.
sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
Valgrind jest łatwo użyteczny dla kodu C / C ++, ale może być nawet używany dla innych języków, jeśli jest poprawnie skonfigurowany (zobacz to dla Pythona).
Aby uruchomić Valgrind , przekaż plik wykonywalny jako argument (wraz z parametrami do programu).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
Flagi to w skrócie:
--leak-check=full
: „każdy wyciek zostanie szczegółowo pokazany”--show-leak-kinds=all
: Pokaż wszystkie „określone, pośrednie, możliwe, osiągalne” rodzaje wycieków w „pełnym” raporcie.--track-origins=yes
: Preferuj użyteczną wydajność niż prędkość. Śledzi pochodzenie niezainicjowanych wartości, które mogą być bardzo przydatne w przypadku błędów pamięci. Rozważ wyłączenie, jeśli Valgrind jest niedopuszczalnie powolny.--verbose
: Może opowiedzieć o nietypowym zachowaniu programu. Powtórz, aby uzyskać więcej szczegółowości.--log-file
: Napisz do pliku. Przydatne, gdy dane wyjściowe przekraczają przestrzeń terminala.Na koniec chciałbyś zobaczyć raport Valgrind, który wygląda następująco:
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)
Więc masz wyciek pamięci, a Valgrind nie mówi nic znaczącego. Być może coś takiego:
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)
Rzućmy okiem na kod C, który również napisałem:
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
Cóż, utracono 5 bajtów. Jak to się stało? Raport o błędzie mówi tylko
main
i malloc
. W większym programie polowanie byłoby bardzo kłopotliwe. Dzieje się tak z powodu sposobu skompilowania pliku wykonywalnego . W rzeczywistości możemy uzyskać szczegółowe informacje o tym, co poszło nie tak, wiersz po wierszu. Przekompiluj swój program z flagą debugowania (używam gcc
tutaj):
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
Teraz, dzięki tej kompilacji debugowania, Valgrind wskazuje dokładną linię kodu przydzielającą pamięć, która wyciekła! (Sformułowanie jest ważne: może nie być dokładnie miejsce wycieku, ale to , co wyciekło. Śledzenie pomaga ustalić, gdzie ).
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
problemy typu.Czasami twoje przecieki / błędy mogą być ze sobą powiązane, podobnie jak IDE, które odkrywa, że nie wpisałeś jeszcze nawiasu zamykającego. Rozwiązanie jednego problemu może rozwiązać inne, więc poszukaj takiego, który wygląda na dobrego winowajcę, i zastosuj niektóre z tych pomysłów:
gdb
) i poszukaj błędów warunku wstępnego / warunku końcowego. Chodzi o to, aby śledzić wykonanie programu, koncentrując się na czasie życia przydzielonej pamięci.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)
A kod:
#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;
}
Jako asystent nauczyciela często widziałem ten błąd. Uczeń korzysta ze zmiennej lokalnej i zapomina zaktualizować oryginalny wskaźnik. Błąd polega na zauważeniu, że w realloc
rzeczywistości może przenieść przydzieloną pamięć w inne miejsce i zmienić lokalizację wskaźnika. Następnie wychodzimy, resizeArray
nie mówiąc,
array->data
gdzie tablica została przeniesiona.
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)
A kod:
#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;
}
Zauważ, że Valgrind wskazuje nam skomentowaną linię kodu powyżej. Tablica o rozmiarze 26 jest indeksowana [0,25], dlatego *(alphabet + 26)
zapis jest nieprawidłowy - jest poza zakresem. Nieprawidłowy zapis jest typowym wynikiem błędów typu „off-by-one”. Spójrz na lewą stronę operacji przypisania.
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)
A kod:
#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 wskazuje nam na skomentowaną linię powyżej. Spójrz na ostatnią iterację tutaj, czyli
*(destination + 26) = *(source + 26);
. Jednak *(source + 26)
ponownie jest poza zakresem, podobnie jak nieprawidłowy zapis. Nieprawidłowe odczyty są również częstym skutkiem błędów typu off-by-one. Spójrz na prawą stronę swojej operacji przypisania.
Skąd mam wiedzieć, że wyciek jest mój? Jak znaleźć wyciek, gdy używam kodu innej osoby? Znalazłem wyciek, który nie jest mój; powinienem coś zrobić? Wszystkie są uzasadnione pytania. Po pierwsze, dwa przykłady ze świata rzeczywistego, które pokazują dwie klasy typowych spotkań.
#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;
}
To jest prosty program: odczytuje ciąg JSON i analizuje go. Podczas tworzenia używamy wywołań bibliotek, aby wykonać za nas analizę. Jansson dokonuje niezbędnych alokacji dynamicznie, ponieważ JSON może zawierać własne zagnieżdżone struktury. Nie oznacza to jednak, że decref
„uwolnimy” pamięć przekazaną nam z każdej funkcji. W rzeczywistości ten kod, który napisałem powyżej, generuje zarówno „Invalid read”, jak i „Invalid write”. Te błędy znikają, gdy usuniesz decref
linię dla value
.
Czemu? Zmienna value
jest traktowana jako „pożyczona referencja” w Jansson API. Jansson śledzi swoją pamięć za Ciebie, a Ty po prostu musisz mieć decref
niezależne od siebie struktury JSON. Lekcja tutaj:
przeczytaj dokumentację . Naprawdę. Czasami trudno to zrozumieć, ale mówią ci, dlaczego tak się dzieje. Zamiast tego mamy
istniejące pytania dotyczące tego błędu pamięci.
#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;
}
Co jest nie tak z tym kodem ? Ciągle wycieka dla mnie ~ 212 KB pamięci. Pomyśl o tym przez chwilę. Włączamy i wyłączamy SDL. Odpowiedź? Nie ma w tym niczego złego.
Na początku może to brzmieć dziwnie . Prawdę mówiąc, grafika jest niechlujna i czasami trzeba zaakceptować niektóre przecieki jako część standardowej biblioteki. Lekcja tutaj: nie musisz tłumić każdego wycieku pamięci . Czasami wystarczy stłumić wycieki, ponieważ są to znane problemy, z którymi nie można nic zrobić . (To nie jest moja zgoda na ignorowanie twoich własnych przecieków!)
Skąd mam wiedzieć, że wyciek jest mój?
To jest. (W każdym razie na 99%)
Jak znaleźć wyciek, gdy używam kodu innej osoby?
Są szanse, że ktoś już to znalazł. Wypróbuj Google! Jeśli to się nie powiedzie, użyj umiejętności, które ci przekazałem powyżej. Jeśli to się nie powiedzie i najczęściej widzisz wywołania API i niewiele własnego śladu stosu, zobacz następne pytanie.
Znalazłem wyciek, który nie jest mój; powinienem coś zrobić?
Tak! Większość interfejsów API ma sposoby zgłaszania błędów i problemów. Użyj ich! Pomóż oddać narzędzia, których używasz w swoim projekcie!
Dzięki, że zostałeś ze mną tak długo. Mam nadzieję, że się czegoś nauczyłeś, ponieważ starałem się dotrzeć do szerokiego spektrum ludzi dochodzących do tej odpowiedzi. Mam nadzieję, że po drodze zapytałeś kilka rzeczy: Jak działa alokator pamięci w C? Czym właściwie jest wyciek pamięci i błąd pamięci? Czym się różnią od segfaultów? Jak działa Valgrind? Jeśli miałeś którykolwiek z nich, proszę zaspokoić swoją ciekawość:
memcheck
narzędzie jest domyślnie włączone?
memcheck
podręcznika mówi nam, że jest to domyślne narzędzie:--tool=<toolname> [default: memcheck]
Spróbuj tego:
valgrind --leak-check=full -v ./your_program
Dopóki valgrind jest zainstalowany, będzie przeglądał twój program i informował, co się stało. Może dostarczyć wskazówek i przybliżonych miejsc, w których można znaleźć wycieki. Jeśli segfaultinging, spróbuj go przepuścić gdb
.
your_program
== nazwa pliku wykonywalnego lub jakiekolwiek polecenie, którego używasz do uruchamiania aplikacji.
Możesz biegać:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Możesz utworzyć alias w pliku .bashrc w następujący sposób
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
Więc kiedy chcesz sprawdzić wycieki pamięci, po prostu zrób to
vg ./<name of your executable> <command line parameters to your executable>
Spowoduje to wygenerowanie pliku dziennika Valgrind w bieżącym katalogu.