C++中 的内存错误与泄漏
内存错误类型
对于程序员来说,Linux中有两种可访问的存储器 1.用户程序运行的虚拟存储空间 2.寄存器存储器 最常见的内存错误是碰到"Segmentation violation"的错误,这是内存错误产生位置的警告信息,可以通过gdb来定位.以下讨论是一些不那么明显的错误内存错误
- 堆内存错误Heap memory errors:
- 试图释放free已经释放free的内存
- 释放free并未分配的内存
- 试图写已经释放free的内存
- 试图写还未分配的内存
- 内存分配错误
- 读/写动态分配数组越界
- 栈内存错误(本地变量)
- 读/写静态数组越界(数组索引溢出,索引大于数组长度或为负值)
- 函数指针损坏: 传递无效的函数指针并错误地调用
内存泄漏
定义:内存分配却不释放而引起一个应用程序消耗内存,减少了其他应用程序的可用内存,最终引起系统与硬盘交换虚拟内存,使得应用程序到达内存资源限制时变慢或崩溃.这也可能导致系统也停止工作
- 许多C库函数分配malloc的内存必须释放free,例如strdup
...
char *oldString = "Old String";
char newStrig = strdup(oldString);
if(newString == ENOMEM) ... // Fail!!!!
...
free(newString)
...
注意,这里不能用C++的delete,而必须用free
- 必须释放free分配malloc过的内存
char *textString = malloc(128*sizeof(char));
if(textString == ENOMEM) ... // Fail!!!!
...
free(textString); // Don't free if allocation failed
注意:检查内存分配错误,不要释放没有分配的内存
- 必须删除delete已创建new的内存
ptr = new ClassTypeA;
...
delete ClassTypeA;
- 继承,多态和错误的删除
BaseClass* obj_ptr = new DerivedClass; // Allowed due to polymorphism. ...
delete obj_ptr; // this will call the destructor ~Parent() and NOT ~Child()
注意:这里就看出将基类的析构函数声明为虚拟函数的必要性了,在强制类型转换时也要注意内存泄漏的可能, 用C++的cast转换函数可以防止这种错误,编译会警告你
- 指针的重新赋值错误导致悬挂指针
如果指针在被释放之前被重新赋予一个新值,将会产生悬挂指针和内存泄漏
char *a = malloc(128*sizeof(char));
char *b = malloc(128*sizeof(char));
b = a;
free(a);
free(b); // will not free the pointer to the original allocated memory.
- 默认的拷贝构造函数也许不会给出正确的结果
拷贝构造函数以指针复制分配内存.如果需要在析构函数检查并删除之.以值传递类会调用拷贝构造函数分配内存.注意,当处理指针时,缺省的拷贝构造函数可能不会给出你想要的结果,它不知道如何去拷贝指针指向的内容.定义一个空的赋值操作符可禁止使用缺省的拷贝构造函数
ClassA& operator=(const ClassA& right_hand_side);
Good practice: 在使用和删除指针前用断言检查之
assert(ptr !=0)
内存损坏Memory Corruption
定义:由于无意疏忽而对于内存中数据的操作使得内存没经显式地分配就发生改变 或改变指向内存中特定区域的指针.
- 缓存溢出Buffer overflow
例1.覆盖了超过已分配的长度的内存
char *a = malloc(128*sizeof(char));
memcpy(a, data, dataLen); // Error if dataLen too long.
例2:数据索引越界:数据索引大于数组长度或为负值
ptr = (char *) malloc(strlen(string_A)); // Should be (string_A + 1) to account for null termination.
strcpy(ptr, string_A); // Copies memory from string_A which is one byte longer than its destination ptr
溢出了一个字节
- 在内存分配赋值之前使用其地址
struct *ABC_ptr;
x = ABC_ptr->name;
此例中的内存地址为空或随机的.
- 使用已释放的指针
char *a = malloc(128*sizeof(char));
..
..
free(a);
cout << a << endl; // This will probably work but dangerous.
... Do stuff. Probable overwriting of freed memory.
cout << a << endl; // No longer the same contents. Memory overwritten by new stuff.
- 释放/删除已经释放/删除过的内存
释放一个指针两次
char *a = malloc(128*sizeof(char));
free(a);
... Do stuff
free(a); // A check for NULL would indicate nothing.
// This memory space may be reallocated and thus we may be freeing
// memory we do not intend to free or portions of another block of
// memory. The size of the block of memory allocated is often held
// just before the memory block itself..
- 释放还没动态分配的内存
struct ABC abc;
struct ABC *abc_ptr = &abc;
...
free(abc_ptr);
- 不正确的删除:删除delete应与创建new配套一致
正确的对应操作是new/delete和new [] / delete[]
ClassABC *abc_ptr = new ClassABC[100];
...
delete [] abc_ptr;
若用"delete abc_ptr"就错了 在C++类中不要使用malloc()/free(),它不会调用构造和析构函数,也不要把它们与new/delete混起来用 比如 Free() 不能释放由new创建的内存,delete也不能用来释放由 malloc()分配的内存
- 异常Exception错误
释放从未分配的内存.如果你用构造函数来分配内存,但是在分配内存之前一个exeption被抛出,析构函数必需知道这个事实.否则你可能会去释放从未分 配的内存. 相反的,如果析构函数抛出一个异常,下面的释放内存的步骤将可能不被执行.这适用于抛出异常的析构函数及其在栈调用中嵌套的析构函数
- 指针的生命周期
从栈中返回一个指针的函数在其调用者那里将不再有效
int *get_ii()
{
int ii; // Local stack variable
ii = 2;
return ⅈ
}
main()
{
int *ii;
ii = get_ii(); // After this call the stack is given up by the routine
// get_ii() and its values are no longer safe.
... Do stuff
.. ii may be corrupt by this point.
}
在函数中的局部变量(被大括号括起来的)也是如此
- 不正确的函数参数传递
不正确传递作为函数参数的指针,可能被不正确地释放
- 混合对象基类和派生类
当对象作为函数参数进行值传递,如果混用对象的基类和派生类,要明白你所丢失的 function_A(BaseClass baseClass_ptr)
{ ... }
// Call to function function_A(derivedClass_ptr); // Note that much of the information contained
// in the derived class will not be passed into
// the function including virtual destructors.
- 对象的拷贝
不要用memcpy或任何基于位的拷贝函数来拷贝对象,它将不会执行类的构造函数. 什么人会这样做呢? 传递一个对象在va_arg()的参数列表中会导致一个位拷贝,并不会调用拷贝构造函数