C/C++内存管理(随笔)

1:内存管理,从广义上来看,其实可以说是资源管理。

而“资源”包括:内存,IO资源(文件句柄,设备句柄),GDI,(多线程环境下的)各种临界区资源。

要想实现有效的资源管理,有效的资源回收机制,就必须确保一个前提条件:每个资源都有他们的所有者。即有相应的指针、引用或句柄来指向这些资源。

 

2:频繁使用new 和 delete 进行不定大小的内存分配,将导致堆上的内存碎片,从而导致程序性能的下降。这是因为,当存在大量碎片时,一方面这些碎片经常不能满足new的要求,导致低效的利用率,同时,OS也会花费更多的时间在堆链表中寻找可用的内存块。所以:必须合理的控制内存的分配

但当你必须要使用new 和delete时,你不得不自己控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new和delete

一个防止堆破碎的通用方法是从不同固定大小的内存池中分配不同类型的对象,对每个类重载new 和delete就提供了这样的控制,这样就能提供针对每一个类的相应的内存管理方案。

 

 

3:C++用类对资源管理的准则:对象创建时获取资源,对象销毁时释放资源;对应的也就是:在构造函数中进行资源分配,而在析构函数中释放所有在构造函数中申请的资源。如果能够严格按照这一准则,就程序中将不会产生任何资源泄漏问题!!

(引文:

一个指针,一个句柄,一个临界区状态只有在我们将它们封装入对象的时候才会拥有所有者。这就是我们的第一规则:在构造函数中分配资源,在析构函数中释放资源。

  当你按照规则将所有资源封装的时候,你可以保证你的程序中没有任何的资源泄露。这点在当封装对象(EncapsulatingObject)在栈中建立或者嵌入在其他的对象中的时候非常明显。但是对那些动态申请的对象呢?不要急!任何动态申请的东西都被看作一种资源,并且要按照上面提到的方法进行封装。这一对象封装对象的链不得不在某个地方终止。它最终终止在最高级的所有者,自动的或者是静态的。这些分别是对离开作用域或者程序时释放资源的保证。

 

4:new实际的操作有两个步骤组成:1:malloc, 2: constructor, 对应的delete为: 1:destructor, 2:free.

所以,实际的初始化操作都是在内存分配成功后才执行的。

 

5:如何一个类对象没有显式地提供构造函数和析构函数,那么其行为特性与基本数据类型的行为特性在内存构建方面大体上没有什么区别。

 

6:基于第4,5点,我们可以分析delete和delete[]的区别:

例:Object* obj = newObject[num];

// deleteobj;//obj占用的内存空间会被释放,但只有obj[0]的构造函数会被调用

  delete[]obj;//obj占用的内存空间会被释放,并且obj[0]~obj[num-1]所有数组中元素的构造函数都会被调用

所以两种形式,在Object没有动态内存分配的情况下(也即没有指针成员,或只有默认构造函数),是可以认为等效的。但是,如果有了动态内存分配的情况,则两者是不同的,决不可混用!

 

7:new[] 和 delete[]

C++将对象数组的内存分配作为一个单独的操作,而不同于单个对象的内存分配。即new[] 和 delete[]并不是通过循环来一次次的调用new和delete来实现的。实际上,它会首先一次性完成所需内存大小的分配(实际上是也是调用operator new()来完成内存分配的),然后再一次性的调用数组中每一个元素的构造函数。

 

8:关于重载operator new 和operator delete 以及 operator new[] 和 operator delete[]

由第4点和第7点的分析,我们可以看到,new 和delete的工作都是要分成两步来完成的,而通过重载operator new/delete, 实际上,是给了我们控制malloc/free的机会,而对于constructor/destructor的调用是C++语言内置的行为,并不受程序员的控制!

注:实际上,这也很好理解:我们不需要再在operator new/delete中对对象的构造过程进行什么控制了。这样做,一来导致分工不明确,二来完全没必要,因为,如果想要对构造过程加以控制和修改,你只需要修该相应的类的构造/析构函数就行了,没必要在operatornew/delete中越俎代庖了。就让operator new/delete专心去管怎样有效的使用内存这一件事情吧!

 

9:如何管理类中的成员指针?

解决方案1:只要类中有指针,就必须定义自己版本的拷贝构造函数和赋值构造函数,并且,执行第3条准则。即:使得类的具有值对象的行为。

解决方案2:明确禁止拷贝和赋值构造函数,使得所有的类的赋值具有指针行为

解决方案3:智能指针(有两种实现方式:引用计数、auto_ptr)

解决方案4:内存池

解决方案5:全自动的垃圾回收机制(原理:垃圾回收的时候,只需要扫描 bss 段, data 段以及当前被使用着的栈空间,找到可能是动态内存指针的量,把引用到的内存递归扫描就可以得到当前正在使用的所有动态内存了。)

 

10:对于内存的使用优先选择: 栈 --> 堆--> 

 

11:一个管理内存的疑惑: 内存中动态生成的对象很多,相互调用和引用的关系很复杂,当程序在一直往下写的时候,实在已经不知道将要被引用的对象是否还存在,这时应该怎么办?

答:应该重新设计数据结构,从根本上解决对象管理的混乱局面!

在具体实施措施上,应该在类层次上实现对内存的管理,并且整个项目在内存管理的策略上要保持某种一致性。并参考第9点中的各种策略,结合不同的应用需求,具体问题具体分析。

 

12:可以使用auto_ptr作为类中的成员变量,从而能够防止当构造函数失败时,因为没有调用析构函数而造成的资源泄漏问题。

auto_ptr的实质:一种资源封装类,用栈对象来封装动态分配的资源,同时禁止auto_ptr自身的动态分配行为,即不允许auto_ptr在堆上创建实例。

 

13:智能指针的使用,使得资源泄漏的问题限制到局部作用域中,从而对于内存方面的编程错误的排查也能够局部化,更有利于发现和排错。 

 

14:引用计数:共享的所有权

 

15:将原有代码转换为资源管理代码

如果你是一个经验丰富的程序员,你一定会知道找资源的bug是一件浪费时间的痛苦的经历。我不必说服你和你的团队花费一点时间来熟悉资源管理是十分值得的。你可以立即开始用这个方法,无论你是在开始一个新项目或者是在一个项目的中期。转换不必立即全部完成。下面是步骤。

(1)       首先,在你的工程中建立基本的Strong Pointer。然后通过查找代码中的new来开始封装裸指针。

(2)       最先封装的是在过程中定义的临时指针。简单的将它们替换为auto_ptr并且删除相应的delete。如果一个指针在过程中没有被删除而是被返回,用auto_ptr替换并在返回前调用release方法。在你做第二次传递的时候,你需要处理对release的调用。注意,即使是在这点,你的代码也可能更加"精力充沛"--你会移出代码中潜在的资源泄漏问题。

(3)       下面是指向资源的裸指针。确保它们被独立的封装到auto_ptr中,或者在构造函数中分配在析构函数中释放。如果你有传递所有权的行为的话,需要调用release方法。如果你有容器所有对象,用Strong Pointers重新实现它们。

(4)       接下来,找到所有对release的方法调用并且尽力清除所有,如果一个release调用返回一个指针,将它修改传值返回一个auto_ptr。

(5)       重复着一过程,直到最后所有new和release的调用都在构造函数或者资源转换的时候发生。这样,你在你的代码中处理了资源泄漏的问题。对其他资源进行相似的操作。

(6)       你会发现资源管理清除了许多错误和异常处理带来的复杂性。不仅仅你的代码会变得精力充沛,它也会变得简单并容易维护。

 

 16:如何对付内存泄漏?

很明显,当你的代码中到处充满了new 操作、delete操作和指针运算的话,你将会在某个地方搞晕了头,导致内存泄漏,指针引用错误,以及诸如此类的问题。

这和你如何小心地对待内存分配工作其实完全没有关系:代码的复杂性最终总是会超出你能够付出的时间和努力

于是随后产生了一些成功的技巧,它们依赖于将内存分配(allocations)与重新分配(deallocation)工作隐藏在易于管理的类型(class)之后。标准容器(standard

 containers)是一个优秀的例子。它们(这些类)不是通过你而是自己为元素管理内存,从而避免了产生糟糕的结果。

这是因为:标准容器类内部有一个allocator,可以实现对内存分配的自我管理

 

17:好好利用STL,减少显式的内存管理的工作

(1)通过使用函数对象标准算法(standard algorithm),我们可以避免使用指针——例如使用迭代子(iterator);

(2)如果你的程序还没有包含将显式内存管理减少到最小限度的库,那么要让你程序完成和正确运行的话,最快的途径也许就是先建立一个这样的库

 

18:禁止产生堆对象

你决定禁止产生某种类型的堆对象,这样,你就可以自己创建一个资源封装类,该类对象只能在栈中产生,这样就能在异常的情况下自动释放封装的资源。

 

19:如何禁止产生堆对象?

operator new/delete 设为private

 

20:如何禁止产生栈对象?

将构造/析构函数设为protected,然后用静态方法createInstance/destroyInstance来完成对象的创建和销毁(两个函数在其内部会调用构造/析构函数)

如果一个类不打算作为基类,通常采用的方案就是将其析构函数声明为private。为了限制栈对象,却不限制继承,我们可以将析构函数声明为protected!

  

参考文档:

C++内存管理

http://www.cnblogs.com/qiubole/articles/1094770.html

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值