【原创】常见的内存错误以及如何避免
C++中支持指针操作,一方面极大限度的提高了编程灵活性,但同时带来了安全隐患
通常的内存错误被归结为以下4点:内存泄露,重复释放,坏指针问题和超量写内存
1.内存泄露
在分配了一块内存空间后,如果不再需要这些数据就应当考虑将其释放。
如果被分配的内存空间不再需要时且程序员没有将其释放,那么这块空间将随同程序运行而一直存在。
这在极少情况下也有可能是正常的,但更多的时候是非常危险的。
对于一个需要运行较长时间的程序而言,如果它只是一味的分配而不进行回收,那么系统资源无疑将被耗尽。
内存的泄露通常是由回收失败导致的,如下面的例子:
void Memory_leak()
{
int* list=new int[100];
...
if(End()) return;
...
delete[] list;
}
异常,错误和其他各种throw和catch语句经常是导致内存泄露的原因
还有一种可能引起内存泄漏的原因是忘记了释放一个数据结构的某些部分,例如定义以下结构
struct Student
{
char* name;
int number;
int age;
char* address;
};
//对于Student这个结构有一个相关函数如下
void Memory_Leak()
{
Student* s;
s=new Student;
s->name=new char[100];
...
s-main()->address=new char[50];
...
delete s;
}
在上例中,一个Student结构体被分配了内存并在程序结束的时候被释放,但是这个结构体的一些域并没有被释放
例如name和address都被分配了空间,但他们没有被释放。
注意:结构体被释放并不表示它的域也被释放了
2.重复释放
如果释放一个根本不存在的指针,或对于一个指针重复释放都会引起不必要的麻烦。
可以写一个防止重复释放的小函数,例如下面定义了一个宏,他可以用来避免重复释放
#define SAFE_DELETE(p)
{
if((p)!=NULL)
{
delete (p);
(p)=NULL;
}
}
在一个程序中可能存在多个指针同时指向一块内存空间,对于其中任何一个的释放都是把这块空间进行了回收
请看如下代码
BYTE* temp_buffer1=new BYTE[100];
...
BYTE* temp_buffer2=temp_buffer1;
...
BYTE* temp_buffer3=temp_buffer2;
...
delete[] temp_buffer1;
delete[] temp_buffer2;
delete[] temp_buffer3;
事实上,temp_buffer1,temp_buffer2,temp_buffer3都指向同一块内存区域,无需释放3次,仅仅释放其中之一就可以了
程序访问了一段已经被释放了的内存空间可能引起很多危险,因为一旦内存块被释放,它其中的数据就有可能被应用程序或堆分配管理器修改。恰恰在修改后,这块被占据的内存再次被其他的数据块访问到,后果就不堪设想了。如果你对这个块进行了写操作,也就是说改写了本不该属于你的数据,程序可能就会崩溃
一种避免上述错误发生的方法是当一个指针被释放时,就随即将其赋值为NULL。但是如果这个指针同时存在多分复制件的时候,仅对其中之一赋值为NULL并不能从根本上预防错误的发生。
总之,务必保证被分配的内存块被且仅被释放一次,保证已经被释放了的指针不会再次被调用。如果这个指针曾经被复制过,就必须保证当它所指向的内存区域被释放后,所有的拷贝都不应当被使用。
3.坏指针问题
坏指针是从哪里来的呢?一个不可忽视的根源就是没有被很好的初始化的数据。假设声明了一个本地指针但忘记了初始化它,因为变量是被储存在栈里的,而栈可能充满了那些来自先前活动记录的数据,恰巧这些数据没有被丢弃,那么指针就可能带有他们的值
由堆来分配的对象存在同样的初始化问题。C++能够通过构造函数来帮助用户避免这些问题。当一个类被创建后,构造函数会被自动调用,无论是在堆上还是栈上。
写初始化函数是一个非常好的习惯,他的确非常有效。
如果你不知道正确的初始值,那么就将指针赋为NULL,那样它将不会指向任何数据。
在多数环境下,NULL是一个无效地址,因此任何尝试读取数据的行为都会被立即终止,于是这些错误将相对比较容易定位与修正。
4.超量写内存
第一个常见的错误发生在使用指向数组的指针时,数组的越界访问。
容量为size的数组a是不存在a[size]的,如果使用指针不慎访问了这个地址,就可能会改变程序里其他数据的值而导致程序崩溃
第二个错误是分配了不足的内存
有时程序员会忘记字符串最后需要一个结束符’\0’,如果忽略了这一点就有可能引起错误
操作符的优先权也有可能致使程序员访问了不该访问的地址,不慎的使用*和++或—都有可能导致超量写内存
第三个有可能的错误是构造一个指向运行时栈中某值的指针,并将这个指针返回给函数调用者,例如:
int *p()
{
int i=0;
return &i;
}
由于这个整形变量位于一个活动记录中,而这个记录在函数返回时就会被删除。因此这个指针所指向的内容随时会变丑任意一个值,这完全取决与下一个使用它的函数如何操作