C++内存泄漏/内存越界的各种情况,以及预防与排查

目录

一、内存泄漏

一、介绍

二、几种内存泄露的场景

三、预防与排查

1、valgrind

二、内存越界

一、介绍

二、几种内存越界的情况

三、预防与排查


一、内存泄漏

一、介绍

  • 内存泄漏,是指在程序代码中动态申请的、堆上的内存 由于某种原因、在使用后没有被释放,进而造成内存的浪费。
  • 少部分的内存泄漏不会影响程序的正常运行,不过如果是持续的内存泄漏会耗光系统内存,最终会导致程序卡死甚至系统崩溃。为了避免系统崩溃,在无法申请到内存的时候,要果断调用exit()函数主动杀死进程,而不是试图挽救这个进程。

二、几种内存泄露的场景

1、malloc/new申请的内存没有主动释放

void test1()
{
	char* str = new char[100];
	
	/*delete[] str; 这里忘记delete了 */
}

2、new与free混用,malloc与delete混用

class Base
{
public:
	int* values;
	Base() { values = new int[100]; }
	~Base() { delete[] values; }
};

void test2()
{
	Base* pBase = new Base;

	free(pBase);	
	/* 错误,这样只会释放pBase指向的内存,却不会调用Base的析构函数
	 会导致Base中的values指向的内存无法被释放 */

	delete pBase;	/* 正确 */
}

3、使用new开辟数组时,delete忘记加[]

void test3()
{
	int* vects = new int[100];

	delete vects;	// 这种写法是错误的
	delete[] vects;	// 这种写法是正确的
}

4、基类的析构函数没有定义为虚函数

class A
{
public:
	A() {}
	~A() {}
};

class B : public A
{
public:
	B() { num = new int[100]; }
	~B() { delete[] num; }
private:
	int* num;
};

void main() {
	A* pa = new B();

	delete pa;
	/* 这样只会调用A类的析构函数,而B类中的num就得不到释放
	正确的是应该把A的析构类声明为虚函数 */
}

三、预防与排查

1、valgrind

  • 应用环境:Linux
  • 编程语言:C/C++
  • 使用方法:  编译时加上-g选项,如 gcc -g filename.c  -o filename,使用如下命令检测内存使用情况:
  • 结果输出:#valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./filename,就会看到内存使用报告
  • 设计思路:根据软件的内存操作维护一个有效地址空间表和无效地址空间表(进程的地址空间)
  • 优缺点:能够检测:
    • 使用未初始化的内存 (Use of uninitialised memory)
    • 使用已经释放了的内存 (Reading/writing memory after it has been free’d)
    • 使用超过 malloc分配的内存空间(Reading/writing off the end of malloc’d blocks)
    • 对堆栈的非法访问 (Reading/writing inappropriate areas on the stack)
    • 申请的空间是否有释放 (Memory leaks – where pointers to malloc’d blocks are lost forever)
    • malloc/free/new/delete申请和释放内存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
    • src和dst的重叠(Overlapping src and dst pointers in memcpy() and related functions)
    • 重复free
  • 如何获取:http://valgrind.org/

2、使用智能指针

void test4()
{
	// 以下两种写法效果相同
	shared_ptr<int> p1 = shared_ptr<int>(new int(234));
	shared_ptr<int> p2 = make_shared<int>(234);

	// 还有其他指针指针
	weak_ptr<int>   p3 = weak_ptr<int>(p1);
	unique_ptr<int> p4 = unique_ptr<int>(new int(234));
	auto_ptr<int>   p5 = auto_ptr<int>(new int(234));
}

二、内存越界

一、介绍

  • 内存越界是软件系统主要错误之一,其后果往往不可预料且非常严重。更麻烦的是,它出现的时机是随机的,表现出来的症状是随机的,而且造成的后果也是随机的,这会使程序员很难找出这些 Bug 的现象和本质之间的联系,从而给 Bug 的定位带来极大的困难。一般情况下,内存越界访问可分如下两种:
  • 读越界,即读了不属于自己的数据。如果所读的内存地址是无效的,程序立刻崩溃;如果所读内存地址是有效的,在读的时候不会马上出现问题,但由于读到的数据是随机的,因此它会造成不可预料的后果。
  • 写越界,又称为缓冲区溢出,所写入的数据对别的程序来说是随机的,它也会造成不可预料的后果。

二、几种内存越界的情况

  1. 定义指针的时候未初始化,所以指针指向的时一块随机值,用户并不一定有访问权限。
  2. 分配到的内存比实际上使用的内存要小。
  3. 使用下标访问数组时,下标错误。
  4. 内存已经被释放了,但仍指针来使用这块内存。

三、预防与排查

  1. 定义指针变量的时候,如果暂时不初始化,可以先让它指向 nullptr。
  2. 分配内存时,用户要检查内存是否分配成功。
  3. 使用下标访问数组的时候,要检查是否越界。
  4. 内存被释放之后,指向这块内存的指针也要赋值为 nullptr。

猜你喜欢

转载自blog.csdn.net/iuu77/article/details/129843314