内存泄漏,动态内存无作用域问题,如何避免内存泄漏

本文详细介绍了内存泄漏的概念、原因及分类,探讨了C++中如何有效管理和预防内存泄漏的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无论是C还是C++程序,运行时候的变量主要有三种分配方式:堆分配、栈分配、全局和静态内存分配。内存泄漏主要是发生在堆内存分配方式中,即配置了内存后,所有指向该内存的指针都遗失了,若缺乏语言这样的垃圾回收机制,这样的内存片就无法归还系统。因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以只能在程序运行过程中来判别和诊断。
第一篇: 百度百科:
内存泄漏(Memory Leak)是指程序中己动态分配的内存由于某种原因程序未释放无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃
例如服务器应用软件,需要长时间的运行,不断的处理由客户端发来的请求,如果没有有效的内存管理,每处理一次请求信息就有一定的内存泄漏。这样不仅影响到服务器的性能,还可能造成整个系统的崩溃。
内存泄漏原因
在C语言中,从变量存在的时间生命周期角度上,把变量分为静态存储变量动态存储变量两类。
在内存中供用户使用的内存空间分为三部分:
  • 程序存储区
  • 静态存储区
  • 动态存储区。

程序中所用的数据分别存放在静态存储区和动态存储区中:
静态存储区数据程序的开始就分配好内存区,在整个程序执行过程中它们所占的存储单元固定的,在程序结束时就释放,因此静态存储区数据一般为全局变量。(PS:局部静态变量跟全局变量一样很早就存在于全局数据区。但当第一次运行到该函数,会检测一个位,来判断是否已经初始化。)
动态存储区数据则是在程序执行过程中根据需要动态分配和动态释放的存储单元,动态存储区数据有三类:函数形参变量、局部变量和函数调用时的现场保护与返回地址。由于动态存储变量可以根据函数调用的需要,动态地分配和释放存储空间,大大提高了内存的使用效率.
程序开发的过程使用动态存储变量时,不可避免地面对内存管理的问题。程序中动态分配的存储空间,在程序执行完毕后需要进行释放没有释放动态分配的存储空间而造成内存泄漏,是使用动态存储变量的主要问题。
使用动态存储变量较多和频繁使用函数调用时,就会经常发生内存管理错误,例如:
  • 分配一个内存块并使用其中未经初始化的内容;
  • 释放一个内存块,但继续引用其中的内容;
  • 子函数中分配的内存空间在主函数出现异常中断时、或主函数对子函数返回的信息使用结束时,没有对分配的内存进行释放;
  • 程序实现过程中分配的临时内存在程序结束时,没有释放临时内存。内存错误一般是不可再现的,开发人员不易在程序调试和测试阶段发现,即使花费了很多精力和时间,也无法彻底消除。

产生方式的分类

以产生的方式来分类,内存泄漏可以分为四类:
常发性内存泄漏
发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。
偶发性内存泄漏
发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
一次性内存泄漏
发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。
隐式内存泄漏
程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
第二篇:http://www.cnblogs.com/LoveFishC/archive/2012/08/05/3846221.html
我们一起来看下面这个栗子:
     new *x;
	x = new int[1000];
	delete[] x;
	x = NULL;
   这意味着如果这个地址值(保存在x里)丢失了,就会发生内存泄漏问题。 地址值会因为很多原因而丢失哦,比如因为一个指针变量被无意中改写,例如:
int *x;
x = new int[3000];
x = new int[4000];
delete[] x;
x = NULL;
大家看出来了吗?这是会导致内存泄漏的情况之一。 
会导致内存泄漏的另一种情况是用来保存内存块地址的指针变量作用域问题,例如: 
void foo()
{
    My Class *x;
    x = new MyClass();
}
当foo()函数结束时,指针变量x将超出它的作用域,这意味着它将不复存在,它的值当然就会丢失。  

有两种方法可以用来堵住这样的漏洞:

第一个方法是在return语句之前的某个地方插入一条delete x语句:
void foo()
{
    MyClass *x;
    x = new MyClass();
    delete x;
    x = NULL;
    return;
}
 第二个方法是让函数把内存块的地址返回给它的调用者: 
MyClass *foo()
{
    MyClass *x;
    x = new MyClass();
    return x;
}
内存作用域
  变量都有一个作用域:规定了它们可以在程序的哪些部分使用。 这个作用域通常就是对它们做出声明和定义的函数的函数体,如main函数或某个子函数。   如果被定义在任何一个函数的外部,变量将拥有全局作用域,这意味着它们可以在整个程序中的所有函数里使用。
 不过,应该尽量避免使用全局变量,因为它们往往会让代码变得难以调试和容易出错!   
动态内存不存在作用域的问题,一旦被分配,内存块就可以在程序的任何地方使用。 因为动态内存没有作用域,所以必须由程序员来跟踪它们的使用情况,并在不再需要用到它们的时候把它们及时归还给系统。 这里需要特别注意的是,虽然动态分配的内存块没有作用域,但用来保存其地址的指针变量是受作用域影响的。  

第三篇:
1)尽量把内存的分配和释放操作封装在类里面,自动化地进行;
2)程序运行过程中避免内存的分配操作;(如可以采取预先分配好内存缓冲区的方式)--这条其实很重要;
3)采取STL组件的动态数组代替手工分配对象内存的方式也是一种办法;

第四篇:http://qiusuoge.com/9842.html

1. 用类指针(point-like)对象代替原始指针(raw point)

指针是C++的一种特性。使用指针能给我们带来巨大的方便,但如果使用不当,内存动态分配以后没有及时收回,那么系统的内存资源极有可能泄漏,直至造成程序崩溃。

例如:

class A {……};             //定义一个类型A

定义好一个对象后,我们一般可以用new方法,来创造一个对象实例。

    A *pA = new A;              //动态分配一个内存,用new方法,相应的就需要调用delete方法,将该内存进行回收.

然而,在大量的程序代码中,直接针对每一个new,使用一个delete的方式,存在以下几个问题

1)  程序进行维护期间,开发人员若不慎增加循环、条件控制流语句,程序在未触及到delete时,就运行了return;

2) 在new和delete之间,由于运行了某些操作,导致出现异常。

因此,我们不能百分百保证,delete语句总是会执行。为了确保内存资源总是能够回收,我们可以将指针,用一个对象来进行操作,从而利用C++的“析构函数自动调用机制”自动释放内存资源。在标准的程序库中,提供了auto_ptr和tr1::shared_ptr两种指针对象,我们也可以称之为智能指针,采用这种指针对象,当对象的生命周期结束时,其析构函数将自动调用delete。其用法如下:

std::auto_ptr<A> pA(new A); 

std::tr1::shared_ptr<A> pA(new A); 

这两个指针对象的区别之处在于:多个auto_ptr不能同时指向同一对象;而多个shared_ptr则可以指向同一对象。正是因为这个不同,造成两者在处理copy构造函数和copy assignment操作符时,也不尽相同。auto_ptr的复行为具有特殊性:

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调用形式不匹配,从而造成内存没有成功释放。

string* sArray = new string[100]; 

…… 

delete sArray; 

由于sArray是一个数组,因而上述的100个string对象,就必须调用100次析构函数才能完全释放内存,因而正确的做法是:

delete [] sArray; 

因此,如果你在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ]。如果你在new表达式中不使用[ ],那相应的delete表达式也不能使用[ ]。

3. 在独立的语句中构建智能指针

考虑到如下的函数:

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中,所以就可能在调用这个函数时引发内存泄漏。为了避免这种问题,一般采用两条语句来实现上述代码,如下:

std::tr1::shared_ptr<A> pA(new A); 

f(pA, fun()); 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值