手动分配手动回收

程序在运行的时候需要内存,在c/c++中,栈上的内存(如函数中的局部非静态变量)在使用完之后,操作系统会帮我们自动回收,而通过动态分配得到的 **堆上的内存 **,需要手动释放。

平时写一些小程序可能我们不太注意这一点,因为用到的内存较少,在程序结束后,忘记释放的内存也会被强制回收。如果是编写大型的持续运行的程序,不注意内存释放,会导致内存占用越来越高,影响系统性能或导致进程崩溃。

下面一个测试程序演示内存占用情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

int main(void)
{
int *p = (int*)malloc(sizeof(int)*2000000000);// 分配一块比较大的内存空间(根据电脑内存大小调整)
cout << "p = " << p << endl;// 分配的指针的地址
*p = 10;
cout << "*p = " << *p << endl;// 指针指向的内容
//假设此时p的使命已结束
free(p); // 【使用完后要及时释放掉,否则一直占用内存】

cout << "main runing..." << endl;// 【断点调试,查看-任务管理器-进程-内存】

return 0;
}

直接运行(非调试)会有类似如下的正常结果:

1
2
3
4
p = 0000021DEC181070
*p = 10
main runing...
请按任意键继续. . .

下面调试运行:

  • 在cout语句行加断点,调试运行,同时打开电脑的任务管理器,运行程序后,可以在任务管理器中看到内存的占用突然增大(malloc的作用)而后回到正常(free的作用)。
  • 而如果将free语句注释掉,再次调试运行至cout语句处,在任务管理器可以看到内存始终占用较多。如果此时在cout语句后还要大量代码需要分配内存,可能就会内存分配失败造成程序异常。

建议:使用内存分配函数分配内存时,注意malloc/free, new/delete成对使用。

警惕NULL指针

内存分配失败导致的NULL指针

上面程序malloc分配内存的大小需根据自己实际调整,如果太大会造成内存分配失败

  • 直接运行(非调试)会有类似如下结果,程序没有正常结束:

    1
    2
    p = 0000000000000000
    请按任意键继续. . .
  • 调试运行则会引发异常:

    1
    Exception thrown at 0x00007FF6216F5B96 in 指针.exe: 0xC0000005: Access violation writing location 0x0000000000000000.

原因在于内存分配失败,指针地址为0,即分配为空指针(NULL),给空指针写入内容时就会引发写入异常

建议: 内存分配后,应使用if(p==NULL) 或if(p!=NULL)进行防错处理。

函数的指针参数传入NULL指针

含有指针参数的函数也有可能会误用到NULL指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void show(int *p)
{
cout << "*p = " << *p << endl;
}

int main(void)
{
show(NULL); // 不小心传入了空指针

return 0;
}

调试运行,会引发读入异常

1
Exception thrown at 0x00007FF7BE145B74 in 指针.exe: 0xC0000005: Access violation reading location 0x0000000000000000.

建议:函数中使用指针参数前,应使用if(p==NULL) 或if(p!=NULL)进行防错处理。

警惕野指针

野指针也叫悬挂指针,是指向“垃圾”内存的指针,使用“野指针”会让程序出现不确定的行为。 注意,野指针不是NULL指针, 它比NULL指针更容易犯错,因为它不能通过形如 if (NULL == p)的判断语句来预防,只能我们自己在写代码时多注意。

释放后忘记置NULL

指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针,事实上free或delete只是把指针所指的内存给释放掉,但是指针的值还是这块内存的地址(注:也可能是随机的地址,与编译器有关?在我的编译器上每次都是一个固定的其它值),只不过这块内存已经被回收了不能被该进程再使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

int main(void)
{
int *p = new(int);
cout << "p = " << p << endl;
*p = 10;
delete p;// 删除p后,应将其置空(NULL)
cout << "wild p = " << p << endl;// 此时p为随机值?(原来是值?)

//p = NULL;
//cout << "null p = " << p << endl;

if (p!=NULL)
{
*p = 20;
cout << "*p = " << *p << endl;
}

cout << "mian end" << endl;

return 0;
}
  • 直接运行(非调试),程序没有正常结束:

    1
    2
    3
    p = 0000026D0FEF60B0
    wild p = 0000000000008123
    请按任意键继续. . .
  • 调试运行,会引发写入异常,因为p非空,但无法写操作:

    1
    Exception thrown at 0x00007FF64AC66BF5 in 指针.exe: 0xC0000005: Access violation writing location 0x0000000000008123.
  • 取消上面p=NULL的注释,即可正常运行:

    1
    2
    3
    4
    5
    p = 00000266C89166F0
    wild p = 0000000000008123
    null p = 0000000000000000
    mian end
    请按任意键继续. . .

建议:free或delete之后将相应的指针设置为NULL 。

指针定义后未初始化

指针定义后,在使用前,需要初始化,否则也是野指针,指向不确定:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

int main(void)
{
int *p ;

cout << "p = " << p << endl;

cout << "mian end" << endl;

return 0;
}

我的编译器未编译通过:error C4700: uninitialized local variable ‘p’ used,可能某些编译器可以编译通过,进而引发程序异常。

建议:**定义指针变量的时候尽量初始化,哪怕初始化为NULL也好 **

不应返回局部变量的地址

c/c++中,局部变量是存放在栈中的,它的特点是随函数调用时创建随函数结束时销毁,因此在程序中将局部变量的地址返回后赋值给一个指针,这个指针指向的是一个已经被回收的内存,这也是一种野指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

int* fun()
{
int i = 10;
return &i; // 返回局部变量地址,错误用法!!!
}

int main(void)
{
int *p = fun();// 本想获取i的值
cout << "p = " << p << endl;// p的值不确定

cout << "mian end" << endl;

return 0;
}

运行结果,p的值是随机数(每次运行都不一样):

1
2
3
p = 0000001D686FF5C4
mian end
请按任意键继续. . .

建议:不要在函数中返回局部变量的地址,如果必须返回局部变量的地址,则局部变量需申明为static类型(static变量的生存期是整个程序运行期间)

其它异常导致的内存无法释放

即使在malloc/new后显示调用了free/delete释放内存,但是由于其它异常可能会导致释放内存的free/delete语句得不到执行,也会发生内存泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class Test
{
public:
Test(int x = 0):m_val(x) {};
~Test()
{
cout << "~Test() has been called" << endl;
}

int m_val;
};

int main(void)
{
try
{
Test *t = new Test(100);
cout << "t->m_val" << t->m_val << endl;
throw("throw exception");//人为抛出异常

delete t;//由于上句出现异常,此句无法执行
}
catch (...)// 捕获所有异常
{
cout << "something has gone wrong!" << endl;
}

return 0;
}

运行结果,无法进行delete:

1
2
3
t->m_val100
something has gone wrong!
请按任意键继续. . .

类的析构函数没有被执行,可推知delete语句并没有得到执行。此程序在catch中加个delete 可解决问题,但对于一个庞大的工程时候,很难找出异常的位置。更好的解决方法是使用 智能指针

建议:C++代码代码中多注意使用智能指针。

参考:【C++札记】C/C++指针使用常见的坑