Kilka przykładowych błędów w zarządzaniu pamięcią w C++ oraz jak Valgrind memcheck je raportuje
Program skompilowany z flagami -g -O0, uruchomiony pod memczekiem z opcją –leak-check=full:
> g++ -g -O0 -o program program.cpp > valgrind --tool=memcheck --leak-check=full ./program
1. Czytanie niezainicjalizowanej pamięci
void test1(int &out) { int x; if (x == 0) out = 0; else out = 1; }
Raport:
==6243== Conditional jump or move depends on uninitialised value(s) ==6243== at 0x80483FE: test1(int&) (program.cpp:5) ==6243== by 0x8048431: main (program.cpp:14)
2. Czytanie/zapis pamięci po tym, jak została zwolniona
void test2(int &out) { int *x = new int; delete x; out = *x; }
Raport:
==6277== Invalid read of size 4 ==6277== at 0x80484C7: test2(int&) (program.cpp:5) ==6277== by 0x80484EB: main (program.cpp:11) ==6277== Address 0x4028028 is 0 bytes inside a block of size 4 free'd ==6277== at 0x4004CF1: operator delete(void*) (vg_replace_malloc.c:244) ==6277== by 0x80484C3: test2(int&) (program.cpp:4) ==6277== by 0x80484EB: main (program.cpp:11)
3. Czytanie/zapis przed i za zaalokowanym blokiem pęmieci
void test3(int &out) { int *v = new int[10]; out = v[-1] + v[11]; delete[] v; }
Raport:
==6375== Invalid read of size 4 ==6375== at 0x80484BF: test3(int&) (program.cpp:4) ==6375== by 0x80484FF: main (program.cpp:11) ==6375== Address 0x4028024 is 4 bytes before a block of size 40 alloc'd ==6375== at 0x40057F5: operator new[](unsigned) (vg_replace_malloc.c:195) ==6375== by 0x80484B5: test3(int&) (program.cpp:3) ==6375== by 0x80484FF: main (program.cpp:11) ==6375== ==6375== Invalid read of size 4 ==6375== at 0x80484C7: test3(int&) (program.cpp:4) ==6375== by 0x80484FF: main (program.cpp:11) ==6375== Address 0x4028054 is 4 bytes after a block of size 40 alloc'd ==6375== at 0x40057F5: operator new[](unsigned) (vg_replace_malloc.c:195) ==6375== by 0x80484B5: test3(int&) (program.cpp:3) ==6375== by 0x80484FF: main (program.cpp:11)
4. Czytanie/zapis przed blokiem na stosie
void test4(int &out) { int v[10] = {0}; out = v[-1]; }
Raport:
==6385== Invalid read of size 4 ==6385== at 0x8048416: test4(int&) (program.cpp:4) ==6385== by 0x804843B: main (program.cpp:10) ==6385== Address 0xBEF2F644 is just below the stack ptr.
5. Bufory src i dst w funkcji memcpy nakładają się
#include <memory> void test5() { char buff[1000] = {0}; char *src = &buff[0]; char *dst = &buff[300]; memcpy(dst, src, sizeof(char) * 500); }
Raport:
==6407== Source and destination overlap in memcpy(0xBEA053A4, 0xBEA05278, 500) ==6407== at 0x4006CA6: memcpy (mc_replace_strmem.c:116) ==6407== by 0x80484AF: test5() (program.cpp:7) ==6407== by 0x80484C7: main (program.cpp:12)
6. Pomylenie operatorów delete[ ] i delete
void test6() { int *v = new int[10]; delete v; }
Raport:
==6423== Mismatched free() / delete / delete [] ==6423== at 0x4004CF1: operator delete(void*) (vg_replace_malloc.c:244) ==6423== by 0x80484C3: test6() (program.cpp:4) ==6423== by 0x80484DB: main (program.cpp:9) ==6423== Address 0x4113028 is 0 bytes inside a block of size 40 alloc'd ==6423== at 0x40057F5: operator new[](unsigned) (vg_replace_malloc.c:195) ==6423== by 0x80484B5: test6() (program.cpp:3) ==6423== by 0x80484DB: main (program.cpp:9)
7. Podwójne zwalnianie pamięci
void test7() { int *v = new int[10]; delete[] v; delete[] v; }
Raport:
==6444== Invalid free() / delete / delete[] ==6444== at 0x4004973: operator delete[](void*) (vg_replace_malloc.c:256) ==6444== by 0x80484DA: test7() (program.cpp:5) ==6444== by 0x80484F3: main (program.cpp:10) ==6444== Address 0x416B028 is 0 bytes inside a block of size 40 free'd ==6444== at 0x4004973: operator delete[](void*) (vg_replace_malloc.c:256) ==6444== by 0x80484C9: test7() (program.cpp:4) ==6444== by 0x80484F3: main (program.cpp:10)
8. Wyciek pamięci
void test8() { long long *x = new long long; }
Przykładowy raport:
==6455== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==6455== at 0x4005B65: operator new(unsigned) (vg_replace_malloc.c:163) ==6455== by 0x8048485: test8() (program.cpp:3) ==6455== by 0x80484A1: main (program.cpp:8) ==6455== ==6455== LEAK SUMMARY: ==6455== definitely lost: 8 bytes in 1 blocks. ==6455== possibly lost: 0 bytes in 0 blocks. ==6455== still reachable: 0 bytes in 0 blocks. ==6455== suppressed: 0 bytes in 0 blocks.
Podsumowanie
Właśnie poznałeś osiem podstawowych problemów, z jakimi pomoże Ci poradzić sobie memcheck. W języku programowania, który wymaga ręcznego zarządzania pamięcią, narzędzie to jest po prostu nieocenione. Następnym razem pokażę Ci, jak możesz filtrować błędy. Np. takie, które pochodzą z dynamicznych bibliotek cudzego autorstwa i nie jesteś w stanie ich poprawić.