C++内存管理与内存泄漏及其检测
一、内存错误的分类
a.内存访问错误
对内存进行读或写时发生的错误,可能是读未被初始化的内存单元,也可能是读写错误的内存单元。
b.内存使用错误
主要是在动态请求内存之后没有正确释放产生的错误。
二、内存剖析(典型的c++内存模型)
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。(其实我不太明白既然都是存全局变量的,那为什么要把已初始化的和未初始化的分开在两个段中进行管理)
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
c++不同于C#、Java的一个地方是它可以动态管理内存,但鱼与熊掌两者不可兼得,灵活性的代价是程序员需要花费更多的精力保证代码不发生内存错误。
三、常见的内存访问错误和内存使用错误
具体来说,内存访问错误有下面这几种:访问未被初始化的内存单元、数组访问错误、访问无效的内存单元(0x000000,0x000005等)、写无效内存。
而内存使用错误有:1、请求内存之后没有将它释放,使new和delete成对出现可以避免这样的问题。2、释放一块内存后又再释放一次。
四、例子
#include <iostream>
using namespace std;
int main()
{
char* str1="four";
char* str2=new char[4]; //not enough space
char* str3=str2;
cout<<str2<<endl; //UMR
strcpy(str2,str1); //ABW
cout<<str2<<endl; //ABR
delete str2;
str2[0]+=2; //FMR and FMW
delete str3; //FFM
}
UMR:Uninitialized Memery Read.读未初始化内存
ABW:Array Bound Write.数组越界写
FMR/W:Freed Memery Read/Write.读/写已被释放的内存
FFM:Free Freed Memery.释放已被释放的内存
由以上的程序,我们可以看到:在第5行分配内存时,忽略了字符串终止符"\0"所占空间导致了第8行的数组越界写(Array Bounds Write)和第9行的数组越界读(Array Bounds Read); 在第7行,打印尚未赋值的str2将产生访问未初始化内存错误(Uninitialized Memory Read);在第11行使用已经释放的变量将导致释放内存读和写错误(Freed Memory Read and Freed Memory Write);最后由于str3和str2所指的是同一片内存,第12行又一次释放了已经被释放的空间 (Free Freed Memory)。
这个包含许多错误的程序可以编译连接,而且可以在很多平台上运行。但是这些错误就像定时炸弹,会在特殊配置下触发,造成不可预见的错误。这就是内存错误难以发现的一个主要原因。
内存泄漏的定义
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形:
void MyFunction(int nSize)
{
char* p= new char[nSize];
if( !GetStringFrom( p, nSize ) ){
MessageBox(“Error”);
return;
}
…//using the string pointed by p;
delete p;
}
例一
当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是c函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。
广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。
GDI Object的泄漏是一种常见的资源泄漏:
void CMyView::OnPaint( CDC* pDC )
{
CBitmap bmp;
CBitmap* pOldBmp;
bmp.LoadBitmap(IDB_MYBMP);
pOldBmp = pDC->SelectObject( &bmp );
…
if( Something() ){
return;
}
pDC->SelectObject( pOldBmp );
return;
}
例二
当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很多。
内存泄漏的发生方式:
以发生的方式来分类,内存泄漏可以分为4类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:
char* g_lpszFileName = NULL;
void SetFileName( const char* lpcszFileName )
{
if( g_lpszFileName ){
free( g_lpszFileName );
}
g_lpszFileName = strdup( lpcszFileName );
}
例三
如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:
class Connection
{
public:
Connection( SOCKET s);
~Connection();
…
private:
SOCKET _socket;
…
};
class ConnectionManager
{
public:
ConnectionManager(){}
~ConnectionManager(){
list::iterator it;
for( it = _connlist.begin(); it != _connlist.end(); ++it ){
delete (*it);
}
_connlist.clear();
}
void OnClientConnected( SOCKET s ){
Connection* p = new Connection(s);
_connlist.push_back(p);
}
void OnClientDisconnected( Connection* pconn ){
_connlist.remove( pconn );
delete pconn;
}
private:
list _connlist;
};
例四
假设在Client从Server端断开后,Server并没有呼叫OnClientDisconnected()函数,那么代表那次连接的Connection对象就不会被及时的删除(在Server程序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。
文章出处:http://www.diybl.com/course/3_program/c++/cppjs/20081124/152540_2.html
文章出处:http://www.diybl.com/course/3_program/c++/cppjs/20081124/152540.html
转载于:https://www.cnblogs.com/traveller/archive/2009/04/15/1436506.html
C++内存管理与内存泄漏及其检测相关推荐
- C++ 内存管理中内存泄漏问题产生原因以及解决方法
C++内存管理中内存泄露(memory leak)一般指的是程序在申请内存后,无法释放已经申请的内存空间,内存泄露的积累往往会导致内存溢出. 一.内存分配方式 通常内存分配方式有以下三种: (1)从静 ...
- 内存分配器 mysql_聊MySQL内存管理,内存分配器,操作系统
推荐(免费):mysql视频教程 当用户在使用任何软件(包括MySQL)时遇到内存问题,我们的第一反应就是内存泄漏.正如本文所示,情况并非总是如此. 本文描述了一个关于内存的bug. 所有Percon ...
- 内存分配器 mysql_MySQL内存管理,内存分配器和操作系统的示例分析
MySQL内存管理,内存分配器和操作系统的示例分析 发布时间:2021-01-08 14:06:39 来源:亿速云 阅读:79 作者:小新 这篇文章主要介绍MySQL内存管理,内存分配器和操作系统的示 ...
- Linux内存管理:内存分配:slab分配器
<linux内核之slob.slab.slub> <Linux内核:kmalloc()和SLOB.SLAB.SLUB内存分配器> <Linux内存管理:内存分配:slab ...
- C++内存管理__内存管理(栈、堆(new/delete)、自由存储区(malloc/freee)、全局/静态存储区、常量区)!堆栈内存管理方式的区别
内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄 ...
- 【Linux 内核 内存管理】内存管理架构 ④ ( 内存分配系统调用过程 | 用户层 malloc free | 系统调用层 brk mmap | 内核层 kmalloc | 内存管理流程 )
文章目录 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) 二.内存管理流程 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) " 堆内存 " ...
- SAP专家培训之Netweaver ABAP内存管理和内存调优实践
培训者:SAP成都研究院开发人员Jerry Wang 1. Understanding Memory Objects in ABAP Note1: DATA itab WITH HEADER LINE ...
- Linux内存管理之内存管理单元(MMU)(二)
Linux内存管理之内存管理单元(二) 1.1.什么是MMU 在CPU内部,有一个专门的硬件单元来负责这个虚拟页面到物理页面的转换,它被被称为内存管理单元(Memory Management Unit ...
- Linux内存管理:内存寻址之分段机制与分页机制
目录 Linux 内存寻址之分段机制 前言 分段到底是怎么回事? 实模式的诞生(16位处理器及寻址) 保护模式的诞生(32位处理器及寻址) IA32的内存寻址机制 寻址硬件 IA32的三种地址 MMU ...
- Linux内存管理:内存描述之高端内存
<Linux内存管理:内存描述之内存节点node> <Linux内存管理:内存描述之内存区域zone> <Linux内存管理:内存描述之内存页面page> < ...
最新文章
- 从AI、加密货币到火星任务,一种更强大、更稳定的存储设备
- 杭电2055 另一种
- 偏差、方差、欠拟合、过拟合、学习曲线
- Angular getSimpleChangesStore的实现原理
- Python数据结构和算法
- 一维数组和二维数组创建,输出,Arrays.fill()替换
- 一文搞懂隐马尔可夫模型(HMM)
- matlab 二维低通滤波器,matlab二维低通滤波器
- 一行代码即可删除C盘几十G垃圾,清理c盘垃圾的cmd命令
- H3CSE路由-配置OSPF高级
- echarts2的一个地图demo
- IR2104全桥驱动的自举问题
- 截止失真放大电路_模拟放大器
- K线形态识别—K线反转形态之底部反转形态
- fiddler 证书错误
- 简单的微信使用技巧,你需要掌握的技巧
- Win10系统无法启动的最终解决方案
- Tomcat指定war包路径部署
- Addressable设置的要点
- springboot打成jar后获取resources下文件失败, cannot be resolved to absolute file path because it does not resid