Memory Leaks with Valgrind

úniky v pamäti a ich odhaľovanie pomocou nástroja valgrind

Motivácia

Jazyk C poskytuje niekoľko funkcií zo štandardnej knižnice stdlib, pomocou ktorých viete dynamicky pracovať s pamäťou. Nekontroluje však to, či s pamäťou aj pracujete tak, ako ste si ju rezervovali - či náhodou nečítate z miesta, ktoré nemáte vyhradené alebo naopak nezapisujete na miesto, ktoré vám nepatrí. valgrind je nástroj, ktorý vám pomôže prípadné úniky v pamäti identifikovať.

Ciele

  1. Porozumieť problému únikov v pamäti
  2. Naučiť sa používať nástroj valgrind z príkazového riadku.
  3. Naučiť sa rozumieť výstupu z nástroja valgrind.
  4. Naučiť sa identifikovať úniky v pamäti pomocou nástroja valgrind.

Postup

Krok #1: Up!

V prvom kroku zatiaľ o veľa nepôjde. Vytvoríte funkciu upper(), na ktorej si následne ukážeme použitie nástroja valgrind.

Úloha 1.1

Vytvorte funkciu upper(const char* text), ktorá vráti referenciu na kópiu reťazca text, v ktorom budú všetky písmená veľké.

Na prevod písmen na veľké vám vie pomôcť funkcia toupper() z knižnice ctype.h.

Úloha 1.2

Overte správnosť svojej implementácie na reťazcoch "HeLLo", "world" a "H3ll0 w0rld!".

Pokiaľ ste postupovali správne, funkcia zabezpečí transformáciu každého písmena na veľké, pokiaľ je možné z neho veľké písmeno spraviť.

Krok #2: Introduction to Valgrind

V tomto kroku sa zoznámite s nástrojom valgrind. Naučíte sa ho spustiť s vašim programom a naučíte sa rozumieť výstupu, ktorý produkuje.

Úloha 2.1

Spustite svoj kód pomocou príkazu valgrind a analyzujte výsledok.

Kód spustite v tvare:

valgrind ./upper

Po jeho spustení budete vidieť podobný výstup ako nasledovný:

valgrind ./upper 
==12417== Memcheck, a memory error detector
==12417== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==12417== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==12417== Command: ./upper
==12417== 
hello world! is HELLO WORLD!
==12417== 
==12417== HEAP SUMMARY:
==12417==     in use at exit: 13 bytes in 1 blocks
==12417==   total heap usage: 1 allocs, 0 frees, 13 bytes allocated
==12417== 
==12417== LEAK SUMMARY:
==12417==    definitely lost: 13 bytes in 1 blocks
==12417==    indirectly lost: 0 bytes in 0 blocks
==12417==      possibly lost: 0 bytes in 0 blocks
==12417==    still reachable: 0 bytes in 0 blocks
==12417==         suppressed: 0 bytes in 0 blocks
==12417== Rerun with --leak-check=full to see details of leaked memory
==12417== 
==12417== For counts of detected and suppressed errors, rerun with: -v
==12417== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Číslo 12417 predstavuje číslo procesu. Nás však budú zaujímať informácie v dvoch častiach identifikovaných ako HEAP SUMMARY a LEAK SUMMARY.

Význam niektorých prípadov je nasledovný:

  • "Still reachable" - Referencia na blok v pamäti je stále k dispozícii. Tento prípad je veľmi častý, a aj keď nie je považovaný za kritickú chybu, programátor by mal pred ukončením programu zabezpečiť uvoľnenie takéhoto bloku pamäti.
  • "Definitely lost" - V pamäti zostal vyhradený blok, ale neexistuje naň žiadna referencia. Takýto blok je označený ako stratený (z angl. "lost"), pretože programátor tento blok už nedokáže uvoľniť pred ukončením programu, keďže jeho adresu už nemá.

Kompletný zoznam a význam jednotlivých chýb, ktoré valgrind identifikuje, môžete nájsť na stránkach jeho návodu.

V jednej aj druhej časti sa dozvedáme, že po skončení (in use at exit) ostalo v pamäti aj naďalej 13 bytov. To je spôsobené tým, že po skončení funkčnosti nášho programu sme neuvolnili pamäť, ktorú sme si rezervovali.

Úloha 2.2

Opravte vzniknutý problém tak, aby po skončení programu k výpisu uvedenej chyby nedošlo.

Tento problém vznikol neuvoľnením vyhradenej pamäte pred skončením programu. Aj keď sa nejedná o chybu programu, je dobrým zvykom pridelenú pamäť pred skončením programu uvoľniť.

Krok #3: Invalid Read/Write

Častým zdrojom chýb je prístup k údajom v pamäti, ktorú nemáme vyhradenú - či už z nej chceme čítať alebo do nej chceme zapisovať. Pomocou nástroja valgrind však vieme identifikovať aj takéto problémy.

Úloha 3.1

Demonštrujte príklad identifikovania čítania z pamäte, ktorá nie je vyhradená pre váš program.

Na identifikáciu tohto problému nám poslúži nasledujúci fragment kódu:

#include <stdio.h>
#include <stdlib.h>

int main(){
    int size = 4;
    int* array = (int*)malloc(size * sizeof(int));
    printf("%dth value is %d\n", size, array[size]);
}

Aj napriek tomu, že preklad prebehne bez problémov, valgrind identifikuje chybu pomerne jasne:

==21181== Memcheck, a memory error detector
==21181== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==21181== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==21181== Command: ./main
==21181== 
==21181== Invalid read of size 4
==21181==    at 0x4007BD: main (main.c:21)
==21181==  Address 0x54fa050 is 0 bytes after a block of size 16 alloc'd
==21181==    at 0x4C29BCF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==21181==    by 0x4007A4: main (main.c:19)
==21181== 
4th value is 0
==21181== 
==21181== HEAP SUMMARY:
==21181==     in use at exit: 16 bytes in 1 blocks
==21181==   total heap usage: 1 allocs, 0 frees, 16 bytes allocated
==21181== 
==21181== LEAK SUMMARY:
==21181==    definitely lost: 16 bytes in 1 blocks
==21181==    indirectly lost: 0 bytes in 0 blocks
==21181==      possibly lost: 0 bytes in 0 blocks
==21181==    still reachable: 0 bytes in 0 blocks
==21181==         suppressed: 0 bytes in 0 blocks
==21181== Rerun with --leak-check=full to see details of leaked memory
==21181== 
==21181== For counts of detected and suppressed errors, rerun with: -v
==21181== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Nástroj valgrind teda identifikoval nesprávne čítanie 4 bytov priamo za vytvoreným poľom (0 bytov za alokovaným blokom o veľkosti 16 bytov). Ak je zapnutý pri preklade aj prepínač -g, valgrind napíše aj názov súboru a číslo riadku, kde ku chybe došlo (v tomto prípade súbor main.c na riadku 21).

Úloha 3.2

Identifikujte problém, opravte ho a overte správnosť vašej implementácie.

Úloha 3.3

Demonštrujte príklad identifikovania zápisu do pamäte, ktorá nie je vyhradená pre váš program.

Na identifikáciu tohto problému nám poslúži nasledujúci fragment kódu:

#include <stdio.h>
#include <stdlib.h>

int main(){
    int size = 4;
    int* array = (int*)malloc(size * sizeof(int));
    array[size] = 99;
    printf("%dth value is %d\n", size, array[size]);k
}

Aj napriek tomu, že preklad prebehne bez problémov, valgrind identifikuje chybu pomerne jasne:

==22321== Memcheck, a memory error detector
==22321== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==22321== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==22321== Command: ./main
==22321== 
==22321== Invalid write of size 4
==22321==    at 0x4007BD: main (main.c:20)
==22321==  Address 0x54fa050 is 0 bytes after a block of size 16 alloc'd
==22321==    at 0x4C29BCF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==22321==    by 0x4007A4: main (main.c:19)
==22321== 
==22321== Invalid read of size 4
==22321==    at 0x4007D7: main (main.c:22)
==22321==  Address 0x54fa050 is 0 bytes after a block of size 16 alloc'd
==22321==    at 0x4C29BCF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==22321==    by 0x4007A4: main (main.c:19)
==22321== 
4th value is 99
==22321== 
==22321== HEAP SUMMARY:
==22321==     in use at exit: 16 bytes in 1 blocks
==22321==   total heap usage: 1 allocs, 0 frees, 16 bytes allocated
==22321== 
==22321== LEAK SUMMARY:
==22321==    definitely lost: 16 bytes in 1 blocks
==22321==    indirectly lost: 0 bytes in 0 blocks
==22321==      possibly lost: 0 bytes in 0 blocks
==22321==    still reachable: 0 bytes in 0 blocks
==22321==         suppressed: 0 bytes in 0 blocks
==22321== Rerun with --leak-check=full to see details of leaked memory
==22321== 
==22321== For counts of detected and suppressed errors, rerun with: -v
==22321== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Nástroj valgrind teda identifikoval nesprávny zápis 4 bytov priamo za vytvoreným poľom (0 bytov za alokovaným blokom o veľkosti 16 bytov). Ak je zapnutý pri preklade aj prepínač -g, valgrind napíše aj názov súboru a číslo riadku, kde ku chybe došlo (v tomto prípade súbor main.c na riadku 20).

Úloha 3.4

Identifikujte problém, opravte ho a overte správnosť vašej implementácie.

Diskusia

Upozornenie: Do svojich príspevkov nevkladajte správne riešenia úloh a ani ich od ostatných nežiadajte! Nepoužívajte sprosté slová! Takéto príspevky budú zmazané! Riaďte sa podľa pravidiel etického kódexu.