对于初次接触C语言人来说,内存管理往往是令人十分头疼的事,我记得以前在敲代码时往往也常常会发生很多内存泄露问题,现在把可能发生的问题整理如下:
一.未分配空间就使用:
这种问题往往应该时新手经常犯的问题,比如
#include <stdio.h>
struct num{
int n;
};
int main()
{
struct num *temp;
temp->n=1;
printf("%d\n",temp->n);
}
这个例子中,temp只是一个指针,他并没有指向一个已知的空间,就会导致2中情况发生:
1.有些编译器(笔者的就会)会给temp初始化为NULL,temp->n=1,就会使程序尝试对NULL的地址上存放值,这在c语言上时不被允许的,运行到这部就会报段错误。
2.既然没给temp初始化,那么temp可能就是亿个野指针,胡乱指,这个问题就比较严重了,如果指向代码段,而代码段又是可读的,给他赋值段错误,如果指向堆空间,没有经过malloc,new事先申请,依然会报错。最严重的时没报错,因为有可能指向个你能访问的地址,同时你有权力该值,比如你定义的某个条件变量,这会使你整个程序某个地方错误,而你还不知道错在哪。
个人习惯定义个指针时,就赋初值位NULL,不管编译器会不会给指针赋NULL,阻绝野指针。
二.超过内存界限
这种错误往往发生在数组中,比如
int arry[10]={0};
for(int i=0;i<=10;++i)
{
arry[i]=i;
do something;
}
这个程序随着i的不断自增,会导致出现arry【10】的情况,运行时报 stack smashing dected(检测到堆栈废碎)的错误,当然这种情况在c++中很少遇到,因为c++的容器有at这个小标自动检测是否溢出。当然这么简单的循环不会搞错,但如果是复杂的循环了?还有有时候你定义一个char buf【100】数组,然后执行sprintf(),write(),fwrite()等函数时特别是sprintf()函数,这个函数没有size_t count这个参数,就会导致你后面的字符串一旦超过100,就会出现堆栈废碎这种情况。解决方法也有,简单的就是定义个buf【1024】的数组,或者封装这个函数,进行错误检测。
三.malloc()和new()
malloc()和new()这2个函数其实是会申请内存失败的,一旦申请失败就会返回NULL,如果不判断返回值,直接用的话,就可能对NULL进行操作,导致段错误。还有种情况最让程序员头疼的就是,我们知道malloc()申请的内存要用free()还,new()的要用delete还(至于2个能不能换,涉及了c++的类,不讨论),一般简单的程序没啥问题,都能记得。一旦程序复杂起来了,往往就会不知道还了没,哪边还,这就需要做项目前好好的构思构思,列个框架。当然这种情况在C++中其实已经得到基本解决,就是引用智能指针,智能指针的基本思路就是在调用构造函数是把这个指针的计数减一,为0时delet掉。有关智能指针注意点本篇就不概述了(笔者掌握的也不够熟练)。再提一点一个指针只会free(),delete一次,这也是为什么智能指针需要计数的原因了。而如果释放的指针指针没有置为NULL,就是是这个指针成为野指针,因为free(),delete这2个函数只是把指针指向的内存释放了,指针指向的地址依然存在,复杂的程序你就不知道这个指针是否释放掉了,比如:
int mian()
{
dosomething;
free(p);
dosomething;
if(p!=NULL)
{
*p=...;
....;
}
return 0;
}
你就无法用if(p!=NULL)来进行判断了。所以释放好指针后乖乖的赋位NULL吧。
既然释放内存这么麻烦,那我不释放不就好了!千万别抱这种思想,如果申请的内存不进行释放就会导致内存丢失,刚开始可能没啥问题,而且这种情况在程序退出时会自动释放掉内存。所以不是一直运行的程序也没啥大问题,如果你写的程序客户一直运行不关呢?就会导致不断申请空间,导致空间不够,程序卡死。所以就别抱什么侥幸心理了,乖乖的释放把。
四.函数返回指针
函数的返回值千万不能是个栈空间的指针,即不是全局变量或者动态申请的,因为函数在返回时会自动销毁栈空间,导致返回的指针是个野指针。如果是在c++中因为因为类的缘故,又出现新的情况,(既为浅拷贝和深拷贝)大致如下:大家都知道类的对象在构造是会自动调用构造函数,销毁是会自动调用析构函数。那么如果自己写一个new申请堆空间的构造函数,那么很明显也得在析构函数中加入delete释放堆空间功能(不然会造成内存泄漏)。那么看下面的代码:
class A
{
public:
char *c;
A(char *m)
{
c=new char(10);
strcpy(c,m);
cout<<c<<endl;
}
~A()
{
delete c;
}
};
int main()
{
char m[10]="123";
A a=m;
A b=a;
}
这个函数功能很简单,只是把对象a的值赋值给b而已,但是因为a中有个成员c储存在堆空间中的,当程序运行到A b=a时,b中的成员c只是简单的把a中的成员c的地址拷一份而已,并不会再次申请新的堆空间,这就会导致执行析构函数时会对同一个地址delete2次(即对象a和b的析构函数)而对同一个地址只能释放一次,否则报错,这就是浅拷贝带来的弊端。解决方法很简单,在class中在加一个深拷贝函数即
A(const A &a)
{
c=new char(10);
strcpy(c,a.c);
}
这样在运行到A b=a时,会执行上面的函数而不是系统默认的函数。其实就是在拷贝时在申请一个新的空间后在拷,就不会对同一块空间释放2次了。