内存泄漏和内存溢出
内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
常见的内存错误
-
内存申请未成功但是使用了它,产生原因:
这是编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。
解决方法在使用内存时候记得进行null判断
如果指针 p 是函数的参数,那么在函数的入口 处用 assert(p!=NULL)进行检查。
如果是malloc和new申请的内存记得进行(p!=null)或者(p==null)判断 -
内存分配虽然成功,但是尚未初始化就引用它,犯这种错误主要有两个起因:
一是没有初始化的观念。
二是误以为内存的缺省初值 全为零,导致引用初值错误(例如数组)。
解决方法内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。如果定义的是数组的话,可以这样初始化: int a[10] = {0}; 或者用memset 函数来初始化 memset(a,0,sizeof(a))
-
内存分配成功并且已经初始化,但操作越过了内存的边界,产生原因:
使用数组时经常发生下标“多 1”或者“少 1”的操作
解决方法在使用for循环,或者有边界的操作的时候一定要注意边界处理
-
忘记了释放内存,造成内存泄露,产生原因:
含有这种错误的函数每被调用一次就丢失一块内存。
刚开始时系统的内存充足,你 看不到错误。
终有一次程序突然死掉,系统出现提示:内存耗尽。
这种情况在一个函数里面中间进行了return操作,然后在return之前申请了内存,但是没有释放发生的情况很多比如这样:void Func(void) { char *p = new char[10]; ...... if(某个条件) { return; //在此处直接return 并没有调用delete p 造成内存泄漏 } ......
解决方法
> 动态内存的申请与释放必须配对,程序中 malloc 与 free 的使用次数一定要相同,否 则肯定有错误(new/delete 同理)。
-
释放了内存却继续使用它,产生原因:
一是程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内 存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
二是return 返回了指向栈空间的指针或者引用举个例子(下面是返回的指针,引用类似):此处返回了栈空间的指针,如果对外调用者使用了,会造成不可预估的情况 char *Func(void) { char str[]="hello world"; //str数组创建在函数堆栈上,并用字符串常量来初始化 ...... return str; //该句存在隐患,str指向的内存单元将被释放
这种虽然使用时候没有什么问题是在程序的静态数据区但是这种东西一直存在,设计上感觉还是有问题 const char *Func(void) { (const)char *pStr="hello world"; //字符串常量存放在程序的静态数据区, //pStr指针虽然是在栈上定义的,但当函数返回时,它指向的那个内存地址,也就是字符串"hello world"还是存在的,并没有被释放 return pStr; //返回字符串常量的地址,没有问题 }
三是使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。
自己写代码检测泄漏方法
重载new/delete操作符
重载new/delete操作符,用list或者map记录对内存的使用情况。new一次,保存一个节点,delete一次,就删除节点。
最后检测容器里是否还有节点,如果有节点就是有泄漏。也可以记录下哪一行代码分配的内存被泄漏。
类似的方法:在每次调用new时加个打印,每次调用delete时也加个打印。
检查内存泄漏工具
valgrind工具
工具检测原理