内存泄漏
- 概念:内存泄漏是指我们在内存中申请(new/malloc)了一块内存,但是没有去动手释放(delete/free)内存,导致指针已经消失,而指针所指向的空间还被占用,系统就已经不能控制这块空间了。使用完变量之后却没有及时回收这部分内存,这时我们说发生了内存泄漏。如果发生了内存泄漏有没有及时发现,随着程序的运行,程序占用空间会越来越大,直到消耗完系统的所有内存,系统就会崩溃。
发生原因
- 程序循环new创建出的对象没有及时delete掉,导致内存泄漏。
- delete掉一个void* 类型的指针,导致没有调用到对象的析构函数,析构的所有清理工作都没有去执行从而导致内存的泄漏。
class Object {
private:
void* data;
const int size;
const char id;
public:
Object(int sz, char c):size(sz), id(c){
data = new char[size];
cout << "Object() " << id << " size = " << size << endl;
}
~Object(){
cout << "~Object() " << id << endl;
delete []data;
}
};
int main() {
Object* a = new Object(10, 'A');//Object*指针指向一个Object对象;
void* b = new Object(20, 'B');//void*指针指向一个Object对象;
delete a;//执行delete,编译器自动调用析构函数;
delete b;//执行delete,编译器不会调用析构函数,导致data占用内存没有得到回收;
cout << "Press any key to continue... ..." << endl;
getchar();
return 0;
}
- new创建一组对象数组,内存回收的时候缺只调用了delete而非delete[]来处理,导致只有对象数组的第一个对象的析构函数得到执行并回收了内存,而其他对象所占内存得不到回收。
class Object1
{
int a;
int b;
};
int main() {
Object1* arry1 = new Object1[100];//创建包含100个Object1的对象数组arry1并返回数组首地址;
Object1* arry2 = new Object1[100];//创建包含100个Object1的对象数组arry2并返回数组首地址;
delete []arry1;//回收了数组arry1里的所有对象动态创建时占用的内存空间;
delete arry2;//回收了数组arry2里的第一个对象动态创建时占用的内存空间,导致其他99个对象的内存空间泄露;
cout << "Press any key to continue... ..." << endl;
getchar();
return 0;
}
内存溢出
- 概念:内存溢出out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请一个int,但是给其存了long才能存得下的数,那就是内存溢出。
发生原因
- 内存中加载的数据量过于庞大;
- 代码中存在死循环;
- 递归调用太深,导致栈溢出;
- 内存泄漏最终导致内存溢出。
为什么说strcpy和strncpy是C++的危险函数
- 1.strcpy
- 函数原型为char *strcpy(char *dest,const char *src);
- 函数说明:strcpy函数会将参数src字符串拷贝至参数dest所指的地址。
- 参数说明:dest,我们说的出参,最终得到的字符串。src,入参,因为其有const修饰。表示在此函数中不会也不能修改src的值。
- 返回值:返回dest字符串的起始地址。
- 附加说明:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。次函数很好用,可是它也很危险,如果在使用之前加上相关的长度判断,则会大大降低出错的危险。此函数还有一个特点,就是它在把字符串b拷贝到字符串a的时候,会在拷贝的a字符串的末尾加上一个\0结束标志。这一点不同于strncpy()函数。
- 2.strncpy
- 函数原型为:char *strncpy(char *dest,const char *src ,size_t n);
- 函数说明:strncpy会将参数src字符串拷贝前n个字符至参数dest所指的地址。
- 返回值:返回参数dest的字符串起始地址。
- strncpy的正确用法如下:(size一定要用sizeof(dest)或sizeof(dest)-1,不能误用为sizeof(src)。手工补0,务必将dest的最后一个字节手动置为\0。因为strncpy只在src的长度小于dest时,对剩余的字节填0,而当dest长度远大于src长度时,对多于的自己填\0,会造成很大的性能损失。
strncpy(dest, src, sizeof(dest));
dest[sizeof(dest)-1] = ‘\0’;
野指针
- 指向已经删除的对象或者申请访问受限内存区域的指针,称为野指针。它与空指针不同,野指针无法通过简单地判断是否为NULL避免,只能通过良好的编程习惯来避免。对野指针进行操作很容易造成程序错误。
- 指针变量未初始化:指针变量在被创建未初始化时,并不是空指针,它的缺省值是随机的,会乱指一气。所以指针变量在创建同时就应对其进行初始化,要么将指针设置为NULL,要么让其指向一个合法的内存。
- 指针释放之后未置空:有时指针在free或者delete之后未赋值NULL,有可能被误以为是合法的指针,不能进关注free和delete后的指针名,他们只是将指针所指向的内存空间释放掉而已,但并没有把指针自身消灭,此时,指针指向的就是“垃圾”内存。被释放掉内存空间的指针应该立即将其置为NULL,防止产生野指针。
- 指针操作超越变量作用域
class A {
public:
void Func(void){ cout << “Func of class A” << endl; }
};
class B {
public:
A *p;
void Test(void) {
A a;
p = &a; // 注意 a 的生命期 ,只在这个函数Test中,而不是整个class B
}
void Test1() {
p->Func(); // p 是“野指针”
}
};
由于a的生命周期只是在void Test(void)函数内部,函数结束时a将被析构,所以在函数外使用指针p指向的内存空间已经被释放了,所以p已经是野指针了。
总之,良好的编程习惯可以有效避免这些问题的出现。