Valgrind je open-source nástroj, který slouží především pro ladění paměťových chyb (umí toho víc, např. profilování, ale tím se teď zabývat nebudu). V tomto příspěvu bych se chtěl ukázat jeho možnosti, co se týče detekce problémů při práci s pamětí a to tak, že vždy uvedu jednoduchý příklad kódu (omezím se na jazyk C, ale valgrind zvládá programy napsané v libovolném jazyce, ať už je výsledek kompilovaný či interpretovaný, ovšem hlavně se používá pro programy napsané v C a C++), ve kterém se nachází paměťová chyba a ukázku, jakým výstupem na tuto chybu zareaguje valgrind.
Všechny příklady jsou uvedeny v jazyce C (podle C99), překládány překladačem gcc (verze 4.3) a následně spuštěny přes valgrind (verze 3.4). Pro smysluplný výstup je třeba překládat s ladícími informaci (u gcc se jedná o přepínač -g
). Valgrind je spouštěn s těmito parametry: valgrind --tool=memcheck --leak-check=yes --show-reachable=yes
.
A ještě než se do toho pustím, tak bych rád předeslal, že výčet možností tohoto nástroje není rozhodně kompletní - uvedl jsem pouze chyby, o kterých se domnívám, že patří mezi nejčastější.
Nejdříve uvedu zdrojový kód programu, poté relevantní výstup valgrindu a nakonec zhodnocení, o jakou chybu se přesně jednalo a případné doplňující informace.
#include <stdio.h> int main() { int i; if (i == 0) printf("lucker\n"); return 0; }
==7287== Conditional jump or move depends on uninitialised value(s) ==7287== at 0x4004F8: main (vg1.c:6)
Neinicializované lokální proměnné v jazyce C mají po vytvoření nedefinovanou hodnotu (jelikož se vytváří na zásobníku, tak se většinou jedná o smetí, které na tomto místě bylo před vytvořením oné proměnné). Proto je chybou zjišťovat hodnotu takovéto proměnné, protože tato hodnota nám k ničemu není. To stejné platí v případě, pokud alokujete paměť pomocí malloc()
- tato funkce alokovanou paměť nijak neinicializuje (pokud ji chcete vynulovat, lze místo malloc()
použít calloc()
).
int main() { int *i; *i = 0; return 0; }
==7478== Use of uninitialised value of size 8 ==7478== at 0x4004A4: main (vg2.c:6) ==7478== ==7478== Invalid write of size 4 ==7478== at 0x4004A4: main (vg2.c:6) ==7478== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==7478== ==7478== Process terminating with default action of signal 11 (SIGSEGV) ==7478== Access not within mapped region at address 0x0 ==7478== at 0x4004A4: main (vg2.c:6)
Podobně jako v minulé ukázce, tak i zde je nutné před přístupem na ukazatel nastavit tomuto ukazateli platnou hodnotu (například pomocí malloc()
). Pokud tak neučiníte, výsledek při přístupu na takovýto ukazatel je nedefinovaný.
#include <stdlib.h> int main() { char *i = malloc(10); free(i); *i = 0; return 0; }
==7612== Invalid write of size 1 ==7612== at 0x40055F: main (vg3.c:7) ==7612== Address 0x517a030 is 0 bytes inside a block of size 10 free'd ==7612== at 0x4C2261F: free (vg_replace_malloc.c:323) ==7612== by 0x40055A: main (vg3.c:6)
Po uvolnění alokované paměti pro daný ukazatel již přes něj nelze přistupovat. Valgrind správně hlásí, že došlo k neplatnému zápisu na nultý bajt v bloku 10 bajtů, který ovšem byl uvolněn.
#include <stdlib.h> int main() { char *i = malloc(10); i[10] = 0; free(i); return 0; }
==7903== Invalid write of size 1 ==7903== at 0x40055A: main (vg4.c:6) ==7903== Address 0x517a03a is 0 bytes after a block of size 10 alloc'd ==7903== at 0x4C2391E: malloc (vg_replace_malloc.c:207) ==7903== by 0x40054D: main (vg4.c:4)
Pokud alokujeme pole o velikosti 10 bajtů, tak index posledního prvku je 9 (tato "chyba o jedna" běžně nastává při procházení pole cyklem for). Přístup za hranici alokované paměti je chyba (valgrind hlásí, že došlo k přístupu na první bajt za alokovaným blokem).
#include <stdlib.h> int main() { char *i = "whoops"; free(i); return 0; }
==8534== Invalid free() / delete / delete[] ==8534== at 0x4C2261F: free (vg_replace_malloc.c:323) ==8534== by 0x400504: main (vg5.c:6) ==8534== Address 0x4005fc is not stack'd, malloc'd or (recently) free'd
Paměť, na kterou ukazuje ukazatel a
, nebyla alokována dynamicky pomocí malloc()
, tudíž pokus o uvolnění takovéto paměti je neplatný.
#include <stdlib.h> int main() { char *i = malloc(10); char *j = i; free(i); free(j); return 0; }
==8603== Invalid free() / delete / delete[] ==8603== at 0x4C2261F: free (vg_replace_malloc.c:323) ==8603== by 0x40056B: main (vg6.c:8) ==8603== Address 0x517a030 is 0 bytes inside a block of size 10 free'd ==8603== at 0x4C2261F: free (vg_replace_malloc.c:323) ==8603== by 0x400562: main (vg6.c:7)
Ke každému volání malloc()
patří jedno volání free()
- ne více, ne méně. Pokus o vícenásobné uvolnění paměti je chybou.
#include <stdlib.h> int main() { char *i = malloc(10); return 0; }
==8905== malloc/free: in use at exit: 10 bytes in 1 blocks. ==8905== malloc/free: 1 allocs, 0 frees, 10 bytes allocated. ==8905== ==8905== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==8905== at 0x4C2391E: malloc (vg_replace_malloc.c:207) ==8905== by 0x4004FD: main (vg7.c:4)
Všechnu dynamicky alokovanou paměť, která již není potřeba, je třeba uvolnit, jinak může dojít k problémům (především u aplikací, které běží delší dobu - u jednoduchých prográmků to není třeba, protože paměť uvolní operační systém, ale patří to k dobrým programátorským návykům). Valgrind hlásí nejen to, kolik proběhlo alokací a dealokací, ale i která paměť nebyla uvolněna.
Přidat komentář