2个回答
展开全部
##C++ 之内存泄漏及避免##
@(C++学习)[内存]
###1.内存泄漏简介###
在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放以前分配的内存的bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆:从性能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以致另一个程序失败,而使用户无从查找问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。
内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。内存泄漏可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。
在以下情况,内存泄漏导致较严重的后果:
- 程序运行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理);
- 新的内存被频繁地分配,比如当显示电脑游戏或动画视频画面时;
- 程序能够请求未被释放的内存(比如共享内存),甚至是在程序终止的时候;
- 泄漏在操作系统内部发生;
- 泄漏在系统关键驱动中发生;
- 内存非常有限,比如在嵌入式系统或便携设备中;
- 当运行于一个终止时内存并不自动释放的操作系统(比如AmigaOS)之上,而且一旦丢失只能通过重启来恢复。
**内存泄漏举例**
```cpp
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函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。
- 例二:
当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄 漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很 多。
###2.常见触发情形###
内存泄漏的发生方式:
以发生的方式来分类,内存泄漏可以分为4类:
1. **常发性内存泄漏**。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。
2. **偶发性内存泄漏**。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回 True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以 测试环境和测试方法对检测内存泄漏至关重要。
3. **一次性内存泄漏**。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析 构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:
```cpp
char* g_lpszFileName = NULL;
void SetFileName( const char* lpcszFileName )
{
if( g_lpszFileName ){
free( g_lpszFileName );
}
g_lpszFileName = strdup( lpcszFileName );
}
```
例三
如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。
4. **隐式内存泄漏**。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但 是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一 个例子:
```cpp
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的析 构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作 为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危 害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
###3.避免措施###
1. 用类指针(point-like)对象代替原始指针(raw point)
大家都清楚,要对于C#,指针是C++的一种特性。使用指针能给我们带来巨大的方便,但如果使用不旦,内存动态分配以后没有及时收回,那么系统的内存资源极有可能泄漏,直至造成程序崩溃。
例如:
```cpp
class A {……}; //定义一个类型A
```
定义好一个对象后,我们一般可以用new方法,来创造一个对象实例。
```cpp
A *pA = new A; //动态分配一个内存,用new方法,相应的就需要调用
// delete方法,将该内存进行回收
```
然而,在大量的程序代码中,直接针对每一个new,使用一个delete的方式,存在以下几个问题:
- 程序进行维护期间,开发人员若不慎增加循环、条件控制流语句,程序在未触及到delete时,就运行了return;
- 在new和delete之间,由于运行了某些操作,导致出现异常。
因此,我们不能百分百保证,delete语句总是会执行。为了确保内存资源总是能够回收,我们可以将指针,用一个对象来进行操作,从而利用C++的“析构函数自动调用机制”自动释放内存资源。在标准的程序库中,提供了auto_ptr和tr1::shared_ptr两种指针对象,我们也可以称之为智能指针,采用这种指针对象,当对象的生命周期结束时,其析构函数将自动调用delete。其用法如下:
```cpp
std::auto_ptr<A> pA(new A);
//或:
std::tr1::shared_ptr<A> pA(new A);
```
这两个指针对象的区别之处在于:多个auto_ptr不能同时指向同一对象;而多个shared_ptr则可以指向同一对象。正是因为这个不同,造成两者在处理copy构造函数和copy assignment操作符时,也不尽相同。auto_ptr的复行为具有特殊性:
```cpp
std::auto_ptr<A> pA1(new A);
std::auto_ptr<A> pA2(pA1); //pA2指向对象,而pA1为空
pA1 = pA2; //pA1指向对象,而pA2为空
```
2. 成对使用new与delete时应采用相同的形式
一般来讲,new方法可以用于创建单对对象,也可以用于创建数组对象。同样,针对单个对象与数组,调用delete的形式也不尽相同。如下所示的一个例子,就是new与delete调用形式不匹配,从而造成内存没有成功释放。
```cpp
string* sArray = new string[100];
……
delete sArray;
```
由于sArray是一个数组,因而上述的100个string对象,就必须调用100次析构函数才能完全释放内存,因而正确的做法是:
```cpp
delete [] sArray;
```
因此,如果你在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ]。如果你在new表达式中不使用[ ],那相应的delete表达式也不能使用[ ]。
3. 在独立的语句中构建智能指针
考虑到如下的函数:
```cpp
void f(std::tr1::shared_ptr<A> pA(new A), fun());
```
编译器在调用f函数的具体内容前,首先要处理被传递的各个实参。上述第一个参由两部分构成:执行new A,然后调用std::tr1::shared_ptr构造函数。所在编译器在调用f之前,必须做三件事:
- 1)调用函数fun
- 2)执行new A
- 3)调用tr1::shared_ptr构造函数
然后,对于C++来讲,编译器的执行顺序是不确定的,如果最终的操作顺序是这样:
- 1)执行new A
- 2)调用函数fun
- 3) 调用tr1::shared_ptr构造函数
如果是这样,假如在调用函数fun时,发现了异常,此时new A返回的指针将会遗失,而且没有放入tr1::shared_ptr中,所以就可能在调用这个函数时引发内存泄漏。为了避免这种问题,一般采用两条语句来实现上述代码,如下:
```cpp
std::tr1::shared_ptr<A> pA(new A);
f(pA, fun());
```
###4.内存泄漏扩展###
@(C++学习)[内存]
###1.内存泄漏简介###
在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放以前分配的内存的bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆:从性能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以致另一个程序失败,而使用户无从查找问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。
内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。内存泄漏可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。
在以下情况,内存泄漏导致较严重的后果:
- 程序运行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理);
- 新的内存被频繁地分配,比如当显示电脑游戏或动画视频画面时;
- 程序能够请求未被释放的内存(比如共享内存),甚至是在程序终止的时候;
- 泄漏在操作系统内部发生;
- 泄漏在系统关键驱动中发生;
- 内存非常有限,比如在嵌入式系统或便携设备中;
- 当运行于一个终止时内存并不自动释放的操作系统(比如AmigaOS)之上,而且一旦丢失只能通过重启来恢复。
**内存泄漏举例**
```cpp
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函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。
- 例二:
当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄 漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很 多。
###2.常见触发情形###
内存泄漏的发生方式:
以发生的方式来分类,内存泄漏可以分为4类:
1. **常发性内存泄漏**。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。
2. **偶发性内存泄漏**。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回 True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以 测试环境和测试方法对检测内存泄漏至关重要。
3. **一次性内存泄漏**。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析 构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:
```cpp
char* g_lpszFileName = NULL;
void SetFileName( const char* lpcszFileName )
{
if( g_lpszFileName ){
free( g_lpszFileName );
}
g_lpszFileName = strdup( lpcszFileName );
}
```
例三
如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。
4. **隐式内存泄漏**。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但 是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一 个例子:
```cpp
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的析 构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作 为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危 害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
###3.避免措施###
1. 用类指针(point-like)对象代替原始指针(raw point)
大家都清楚,要对于C#,指针是C++的一种特性。使用指针能给我们带来巨大的方便,但如果使用不旦,内存动态分配以后没有及时收回,那么系统的内存资源极有可能泄漏,直至造成程序崩溃。
例如:
```cpp
class A {……}; //定义一个类型A
```
定义好一个对象后,我们一般可以用new方法,来创造一个对象实例。
```cpp
A *pA = new A; //动态分配一个内存,用new方法,相应的就需要调用
// delete方法,将该内存进行回收
```
然而,在大量的程序代码中,直接针对每一个new,使用一个delete的方式,存在以下几个问题:
- 程序进行维护期间,开发人员若不慎增加循环、条件控制流语句,程序在未触及到delete时,就运行了return;
- 在new和delete之间,由于运行了某些操作,导致出现异常。
因此,我们不能百分百保证,delete语句总是会执行。为了确保内存资源总是能够回收,我们可以将指针,用一个对象来进行操作,从而利用C++的“析构函数自动调用机制”自动释放内存资源。在标准的程序库中,提供了auto_ptr和tr1::shared_ptr两种指针对象,我们也可以称之为智能指针,采用这种指针对象,当对象的生命周期结束时,其析构函数将自动调用delete。其用法如下:
```cpp
std::auto_ptr<A> pA(new A);
//或:
std::tr1::shared_ptr<A> pA(new A);
```
这两个指针对象的区别之处在于:多个auto_ptr不能同时指向同一对象;而多个shared_ptr则可以指向同一对象。正是因为这个不同,造成两者在处理copy构造函数和copy assignment操作符时,也不尽相同。auto_ptr的复行为具有特殊性:
```cpp
std::auto_ptr<A> pA1(new A);
std::auto_ptr<A> pA2(pA1); //pA2指向对象,而pA1为空
pA1 = pA2; //pA1指向对象,而pA2为空
```
2. 成对使用new与delete时应采用相同的形式
一般来讲,new方法可以用于创建单对对象,也可以用于创建数组对象。同样,针对单个对象与数组,调用delete的形式也不尽相同。如下所示的一个例子,就是new与delete调用形式不匹配,从而造成内存没有成功释放。
```cpp
string* sArray = new string[100];
……
delete sArray;
```
由于sArray是一个数组,因而上述的100个string对象,就必须调用100次析构函数才能完全释放内存,因而正确的做法是:
```cpp
delete [] sArray;
```
因此,如果你在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ]。如果你在new表达式中不使用[ ],那相应的delete表达式也不能使用[ ]。
3. 在独立的语句中构建智能指针
考虑到如下的函数:
```cpp
void f(std::tr1::shared_ptr<A> pA(new A), fun());
```
编译器在调用f函数的具体内容前,首先要处理被传递的各个实参。上述第一个参由两部分构成:执行new A,然后调用std::tr1::shared_ptr构造函数。所在编译器在调用f之前,必须做三件事:
- 1)调用函数fun
- 2)执行new A
- 3)调用tr1::shared_ptr构造函数
然后,对于C++来讲,编译器的执行顺序是不确定的,如果最终的操作顺序是这样:
- 1)执行new A
- 2)调用函数fun
- 3) 调用tr1::shared_ptr构造函数
如果是这样,假如在调用函数fun时,发现了异常,此时new A返回的指针将会遗失,而且没有放入tr1::shared_ptr中,所以就可能在调用这个函数时引发内存泄漏。为了避免这种问题,一般采用两条语句来实现上述代码,如下:
```cpp
std::tr1::shared_ptr<A> pA(new A);
f(pA, fun());
```
###4.内存泄漏扩展###
光派通信
2024-09-03 广告
2024-09-03 广告
对光传输设备进行调试和维护,需要掌握以下关键点:1. **熟悉设备性能**:深入了解OTN/DWDM等光传输产品的性能特点,确保能够熟练使用设备。2. **按规范操作**:依据产品说明书和维护手册进行调试和维护,确保操作步骤正确无误。3. ...
点击进入详情页
本回答由光派通信提供
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询