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ć.

Related Posts: