内存泄漏的原因及解决办法_编程基础 | C++片段 指针、多态和内存分配

2f7cd28fcff7f8e1f4296296bb1c29d7.png

本片段将介绍运行期而不是编译期的内存分配

1.变量的内存分配和方法的前期绑定

函数中声明的局部变量与其参数以及簿记数据一起被放置在一个活动记录中。活动记录存储在名为运行期栈(run-time stack)的应用程序内存中。

函数调用

运行期栈创建活动记录 函数结束
销毁运行期栈,释放内存

当创建对象时,对象数据成员的存储也在当前执行函数或者方法的活动记录中。

大多数情况,程序只需要自动内存管理和前期绑定。以下两种情况前期绑定无法解决:

(1)需要利用多态

(2)需要访问的对象在创建它的函数或方法之外

2.需要解决的问题

程序会在编译器判断调用方法的版本,会与声明一个对象时的类型相匹配,而不是和之后定义的派生类相匹配,在此需要一种方法告诉编译器在程序运行的时候判断需要执行的代码,这就是所谓的后期绑定,是多态的特征。为解决这个问题,需要两个工具:指针变量和虚函数。

3.指针和程序的自由存储

(1)C++内存分配

为利用后期绑定,不想让对象成为运行期栈上的活动记录,于是操作系统为代码设置了内存(称为代码存储或者文本存储),为全局变量和静态变量设置了内存(称为静态存储),程序还被给予了额外的内存,称为自由存储(free store)或者应用程序堆(application heap),可以在这里存储数据。

52323bdd002fa65b2c861157585bc0ee.png
图1 程序内存布局示例

new运算符在自由存储中分配内存

运行期栈中的变量所拥有的内存是自动分配与释放的,而自由存储中的变量即使在创建它们的函数或者方法终止之后都会存在。(容易内存泄漏

用指针指向对象,e.g.

MagicBox

注意:如果声明了一个指针变量,但是没有立即创建一个对象供其引用,则应该将这个指针设置为nullptr。如下的赋值是必要的,因为C++不会初始化指针。

ToyBox<int>* myToyPtr=nullptr;

(2)释放内存

当指针变量所指的内存不再需要之后,需要使用delete运算符将其释放,然后将指针变量的值设置为nullptr,表示这个变量不再引用或者指向任何对象。

delete 

如果在此示例中没有将somePtr设置为nullptr,那么somePtr就是应该悬挂指针(dangling pointer),因为这个指针仍然保存被释放对象的地址,悬挂指针是严重错误的来源。

(3)避免内存泄漏

①当在自由存储中创建了对象,但程序无法再访问这个对象时,就发生了内存泄漏。

MagicBox<string>* myBoxPtr=new MagicBox<string>();
MagicBox<string>* yourBoxPtr=new MagicBox<string>();
yourBoxPtr=myBoxPtr;//Results is inaccessible object

42f5c2be9efacd7a53c16b73781a2c5e.png
图2 赋值导致对象不可访问

解决办法:应该将yourBoxPtr初始化为nullptr,或者是用myBoxPtr赋初值。

②当函数或者方法在自由存储中创建了对象,并且由于没有将指针你返回给调用者或者没有将其存储在类数据成员中而丢失了指向对象的指针时,就会发生更加微妙的内存泄漏。

//不合理的函数在自由存储中分配内存
void myLeakyFunction(const double& someItem)
{
    ToyBox<double>* someBoxPtr=new ToyBox<double>();
    someBoxPtr->setItem(someItem);
}//end myLeakyFunction

someBoxPtr存储在运行期栈中,函数结束即销毁,创建的对象存放在自由存储中,无法获取其地址而发生内存泄漏。

解决办法:在函数终止前删除对象;不要再自由存储中分配内存,而是使用局部变量;若函数内部创建的对象再外部会用到,则可以返回指向对象的指针,使用指针的代码段负责删除对象,注释中应该注明,但仍有错误使用的风险,如直接调用而不赋给其他指针。

ToyBox<double>* pluggedLeakyFunction(const double& someItem)
{
    ToyBox<double>* someBoxPtr=new ToyBox<double>();
    someBoxPtr->setItem(someItem);
    return someBoxPtr;
}//end pluggedLeakyFunction

pluggedLeakyFunction(boxValue)//Misused;returned pointer is lost

为了防止内存泄漏,最好的方法不是使用函数返回新创建对象的指针,而是定义一个类,类中的方法完成这一任务。类负责删除自由存储中的对象,确保不会发生内存泄漏。这个类最少有三个部分:在自由存储中创建对象的方法、指向这个对象的数据字段,以及当类的实例不再需要的时候删除这个对象的方法,也就是析构函数。

通常,编译器生成的析构函数对类而言已经足够,但是如果类本身使用new运算符创建了对象,为了安全起见,实现析构函数时应该确保为对象分配的内存被释放。

(4)避免悬挂指针

可能导致悬挂指针的情况

①如果在使用delete之后不将指针变量设置为nullptr

②如果声明了一个指针变量但是不对其赋值

③两个指针指向同一个对象,删除了其中一个指针并置空,另一个指针成为悬挂指针

MagicBox<string>* myBoxPtr=new MagicBox<string>();
MagicBox<string>* yourBoxPtr=myBoxPtr;

delete myBoxPtr;
myBoxPtr=nullptr;//共同指向的对象已不存在
yourBoxPtr->getItem();//无法调用其方法

悬挂指针的解决办法:

  • 初始化或者不需要的时候,指针变量设置为nullptr
  • 减少别名的使用
  • 删除对象时,将所有引用这个被删除对象的别名设置为nullptr

4.虚方法和多态

实现多态需要编译器执行后期绑定,为此必须将基类的方法声明为virtual。

为了实现后期绑定,必须在自由存储中创建变量并使用指针指向这些变量。

关于虚方法的要点:

  • 虚方法是派生类可以重写的方法。
  • 必须实现类的虚方法(纯虚方法不包含在内)。
  • 派生类不需要重写被继承的虚方法的已有实现。
  • 类的任何方法都可以是虚方法。当然,如果不想让派生类重写某些特定的方法,那么这些方法就不应该是虚方法。
  • 析构函数不能是虚方法。
  • 析构函数可以是也应该是虚方法。虚析构函数确保了对象的后代可以正确地释放自身。
  • 虚方法的返回类型不能被重写。

5.数组的动态分配

int arraySize=50;
double* anArray=new double[arraySize];
//可以在程序运行期对arraySize赋值,改变数组的大小

delete []anArray;

数组大小用完后分配更多空间,并将原有数组复制过来

double* oldArray=anArray;                //Copy pointer to array
anArray=new double(2*arraySize)          //Double array size
for(int index=0;index<arraySize;index++) //Copy old array
    anArray[index]=oldArray[index];
delete []oldArray;                       //Deallocate old array

本文参考《C++数据抽象和问题求解》第6版 清华大学出版社 [美]Frank M.Carrano Timothy Henry著 景丽译

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在进行 C++ 多态实验时,可能会出现以下几个常见问题,提供一些解决方法供参考: 1. 编译错误 问题描述:在编译程序时出现错误,无法通过编译。 解决方法:查看编译器给出的错误提示,根据提示进行代码调整。常见错误包括语法错误、类型错误、变量未定义等。需要仔细检查代码,确保语法正确、类型匹配、变量定义清晰。 2. 运行错误 问题描述:程序可以编译通过,但在运行时出现错误。 解决方法:通过调试器(如 gdb 等)等工具进行调试,查看程序运行过程中出现的错误。常见错误包括指针操作错误、数组越界、内存泄漏等。需要仔细检查代码,确保指针操作正确、数组越界问题得到解决、内存得到正确释放。 3. 逻辑错误 问题描述:程序可以编译通过、运行无错误,但结果不符合预期。 解决方法:通过添加调试输出语句、使用断点等方法,逐步调试程序,查看程序执行过程中的变量值是否正确、程序流程是否符合预期。需要仔细检查代码,确保逻辑正确、边界条件得到考虑。 4. 面向对象设计错误 问题描述:程序使用了继承、多态等面向对象特性,但设计不合理,导致程序难以维护、扩展。 解决方法:需要进行面向对象设计的学习和实践,掌握面向对象设计的基本原则,例如单一职责原则、开放封闭原则、依赖倒置原则等。需要仔细考虑程序的设计,确保代码易于维护、扩展、重用。 希望这些解决方法可以帮助您在 C++ 多态实验中遇到问题时进行及时解决。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值