Java的爱好者们经常批评C++中没有提供与Java类似的垃圾回收(Gabage Collector)机制(这很正常,正如C++的爱好者有时也攻击Java没有这个没有那个,或者这个不行那个不够好),导致C++中对动态存储的官吏称为程序员的噩梦,不是吗?你经常听到的是内存遗失(memory leak)和非法指针存取,这一定令你很头疼,而且你又不能抛弃指针带来的灵活性。
在本文中,我并不想揭露Java提供的垃圾回收机制的天生缺陷,而是指出了C++中引入垃圾回收的可行性。请读者注意,这里介绍的方法更多的是基于当前标准和库设计的角度,而不是要求修改语言定义或者扩展编译器。
什么是垃圾回收?
作为支持指针的编程语言,C++将动态管理存储器资源的便利性交给了程序员。在使用指针形式的对象时(请注意,由于引用在初始化后不能更改引用目标的语言机制的限制,多态性应用大多数情况下依赖于指针进行),程序员必须自己完成存储器的分配、使用和释放,语言本身在此过程中不能提供任何帮助,也许除了按照你的要求正确的和操作系统亲密合作,完成实际的存储器管理。标准文本中,多次提到了“未定义(undefined)”,而这大多数情况下和指针相关。
某些语言提供了垃圾回收机制,也就是说程序员仅负责分配存储器和使用,而由语言本身负责释放不再使用的存储器,这样程序员就从讨厌的存储器管理的工作中脱身了。然而C++并没有提供类似的机制,C++的设计者Bjarne Stroustrup在我所知的唯一一本介绍语言设计的思想和哲学的著作《The Design and Evolution of C++》(中译本:C++语言的设计和演化)中花了一个小节讨论这个特性。简而言之,Bjarne本人认为,
“我有意这样设计C++,使它不依赖于自动垃圾回收(通常就直接说垃圾回收)。这是基于自己对垃圾回收系统的经验,我很害怕那种严重的空间和时间开销,也害怕由于实现和移植垃圾回收系统而带来的复杂性。还有,垃圾回收将使C++不适合做许多底层的工作,而这却正是它的一个设计目标。但我喜欢垃圾回收的思想,它是一种机制,能够简化设计、排除掉许多产生错误的根源。
需要垃圾回收的基本理由是很容易理解的:用户的使用方便以及比用户提供的存储管理模式更可靠。而反对垃圾回收的理由也有很多,但都不是最根本的,而是关于实现和效率方面的。
已经有充分多的论据可以反驳:每个应用在有了垃圾回收之后会做的更好些。类似的,也有充分的论据可以反对:没有应用可能因为有了垃圾回收而做得更好。
并不是每个程序都需要永远无休止的运行下去;并不是所有的代码都是基础性的库代码;对于许多应用而言,出现一点存储流失是可以接受的;许多应用可以管理自己的存储,而不需要垃圾回收或者其他与之相关的技术,如引用计数等。
我的结论是,从原则上和可行性上说,垃圾回收都是需要的。但是对今天的用户以及普遍的使用和硬件而言,我们还无法承受将C++的语义和它的基本库定义在垃圾回收系统之上的负担。”
以我之见,统一的自动垃圾回收系统无法适用于各种不同的应用环境,而又不至于导致实现上的负担。稍后我将设计一个针对特定类型的可选的垃圾回收器,可以很明显地看到,或多或少总是存在一些效率上的开销,如果强迫C++用户必须接受这一点,也许是不可取的。
关于为什么C++没有垃圾回收以及可能的在C++中为此做出的努力,上面提到的著作是我所看过的对这个问题叙述的最全面的,尽管只有短短的一个小节的内容,但是已经涵盖了很多内容,这正是Bjarne著作的一贯特点,言简意赅而内韵十足。
下面一步一步地向大家介绍我自己土制佳酿的垃圾回收系统,可以按照需要自由选用,而不影响其他代码。
构造函数和析构函数
C++中提供的构造函数和析构函数很好的解决了自动释放资源的需求。Bjarne有一句名言,“资源需求就是初始化(Resource Inquirment Is Initialization)”。
因此,我们可以将需要分配的资源在构造函数中申请完成,而在析构函数中释放已经分配的资源,只要对象的生存期结束,对象请求分配的资源即被自动释放。
那么就仅剩下一个问题了,如果对象本身是在自由存储区(Free Store,也就是所谓的“堆”)中动态创建的,并由指针管理(相信你已经知道为什么了),则还是必须通过编码显式的调用析构函数,当然是借助指针的delete表达式。
幸运的是,出于某些原因,C++的标准库中至少引入了一种类型的智能指针,虽然在使用上有局限性,但是它刚好可以解决我们的这个难题,这就是标准库中唯一的一个智能指针::std::auto_ptr<>。
它将指针包装成了类,并且重载了反引用(dereference)运算符operator *和成员选择运算符operator ->,以模仿指针的行为。关于auto_ptr<>的具体细节,参阅《The C++ Standard Library》(中译本:C++标准库)。
例如以下代码,
#include < cstring >
#include < memory >
#include < iostream >
class string
public:
string(const char* cstr) { _data=new char [ strlen(cstr)+1 ]; strcpy(_data, cstr); }
~string() { delete [] _data; }
const char* c_str() const { return _data; }
private:
char* _data;
void foo()
::std::auto_ptr < string > str ( new string( " hello " ) );
::std::cout << str->c_str() << ::std::endl;
由于str是函数的局部对象,因此在函数退出点生存期结束,此时auto_ptr<string>的析构函数调用,自动销毁内部指针维护的string对象(先前在构造函数中通过new表达式分配而来的),并进而执行string的析构函数,释放为实际的字符串动态申请的内存。在string中也可能管理其他类型的资源,如用于多线程环境下的同步资源。下图说明了上面的过程。
进入函数foo 退出函数
| A
V |
auto_ptr<string>::auto<string>() auto_ptr<string>::~auto_ptr<string>()
| A
V |
string::string() string::~string()
| A
V |
_data=new char[] delete [] _data
| A
V |
使用资源 -----------------------------------> 释放资源
现在我们拥有了最简单的垃圾回收机制(我隐瞒了一点,在string中,你仍然需要自己编码控制对象的动态创建和销毁,但是这种情况下的准则极其简单,就是在构造函数中分配资源,在析构函数中释放资源,就好像飞机驾驶员必须在起飞后和降落前检查起落架一样。),即使在foo函数中发生了异常,str的生存期也会结束,C++保证自然退出时发生的一切在异常发生时一样会有效。
auto_ptr<>只是智能指针的一种,它的复制行为提供了所有权转移的语义,即智能指针在复制时将对内部维护的实际指针的所有权进行了转移,例如
auto_ptr < string > str1( new string( < str1 > ) );
cout << str1->c_str();
auto_ptr < string > str2(str1); // str1内部指针不再指向原来的对象
cout << str2->c_str();
cout << str1->c_str(); // 未定义,str1内部指针不再有效
某些时候,需要共享同一个对象,此时auto_ptr就不敷使用,由于某些历史的原因,C++的标准库中并没有提供其他形式的智能指针,走投无路了吗?
另一种智能指针
但是我们可以自己制作另一种形式的智能指针,也就是具有值复制语义的,并且共享值的智能指针。
需要同一个类的多个对象同时拥有一个对象的拷贝时,我们可以使用引用计数(Reference Counting/Using Counting)来实现,曾经这是一个C++中为了提高效率与COW(copy on write,改写时复制)技术一起被广泛使用的技术,后来证明在多线程应用中,COW为了保证行为的正确反而导致了效率降低(Herb Shutter的在C++ Report杂志中的Guru专栏以及整理后出版的《More Exceptional C++》中专门讨论了这个问题)。
然而对于我们目前的问题,引用计数本身并不会有太大的问题,因为没有牵涉到复制问题,为了保证多线程环境下的正确,并不需要过多的效率牺牲,但是为了简化问题,这里忽略了对于多线程安全的考虑。
首先我们仿造auto_ptr设计了一个类模板(出自Herb Shutter的《More Execptional C++》),
template < typename T >
class shared_ptr
private:
class implement // 实现类,引用计数
public:
implement(T* pp):p(pp),refs(1){}
~implement(){delete p;}
T* p; // 实际指针
size_t refs; // 引用计数
implement* _impl;
public:
explicit shared_ptr(T* p)
: _impl(new implement(p)){}
~shared_ptr()
decrease(); // 计数递减
shared_ptr(const shared_ptr& rhs)
: _impl(rhs._impl)
increase(); // 计数递增
shared_ptr& operator=(const shared_ptr& rhs)
if (_impl != rhs._impl) // 避免自赋值
decrease(); // 计数递减,不再共享原对象
_impl=rhs._impl; // 共享新的对象
increase(); // 计数递增,维护正确的引用计数值
return *this;
T* operator->() const
return _impl->p;
T& operator*() const
return *(_impl->p);
private:
void decrease()
if (--(_impl->refs)==0)
{ // 不再被共享,销毁对象
delete _impl;
void increase()
++(_impl->refs);
这个类模板是如此的简单,所以都不需要对代码进行太多地说明。这里仅仅给出一个简单的使用实例,足以说明shared_ptr<>作为简单的垃圾回收器的替代品。
void foo1(shared_ptr < int >& val)
shared_ptr < int > temp(val);
*temp=300;
void foo2(shared_ptr < int >& val)
val=shared_ptr < int > ( new int(200) );
int main()
shared_ptr < int > val(new int(100));
cout<<"val="<<*val;
foo1(val);
cout<<"val="<<*val;
foo2(val);
cout<<"val="<<*val;
在main()函数中,先调用foo1(val),函数中使用了一个局部对象temp,它和val共享同一份数据,并修改了实际值,函数返回后,val拥有的值同样也发生了变化,而实际上val本身并没有修改过。
然后调用了foo2(val),函数中使用了一个无名的临时对象创建了一个新值,使用赋值表达式修改了val,同时val和临时对象拥有同一个值,函数返回时,val仍然拥有这正确的值。
最后,在整个过程中,除了在使用shared_ptr < int >的构造函数时使用了new表达式创建新之外,并没有任何删除指针的动作,但是所有的内存管理均正确无误,这就是得益于shared_ptr<>的精巧的设计。
拥有了auto_ptr<>和shared_ptr<>两大利器以后,应该足以应付大多数情况下的垃圾回收了,如果你需要更复杂语义(主要是指复制时的语义)的智能指针,可以参考boost的源代码,其中设计了多种类型的智能指针。
对于需要在程序中拥有相同类型的多个对象,善用标准库提供的各种容器类,可以最大限度的杜绝显式的内存管理,然而标准容器并不适用于储存指针,这样对于多态性的支持仍然面临困境。
使用智能指针作为容器的元素类型,然而标准容器和算法大多数需要值复制语义的元素,前面介绍的转移所有权的auto_ptr和自制的共享对象的shared_ptr都不能提供正确的值复制语义,Herb Sutter在《More Execptional C++》中设计了一个具有完全复制语义的智能指针ValuePtr,解决了指针用于标准容器的问题。
然而,多态性仍然没有解决,我将在另一篇文章专门介绍使用容器管理多态对象的问题。
为什么不在C++语言中增加对垃圾回收的支持?
根据前面的讨论,我们可以看见,不同的应用环境,也许需要不同的垃圾回收器,不管三七二十一使用垃圾回收,需要将这些不同类型的垃圾回收器整合在一起,即使可以成功(对此我感到怀疑),也会导致效率成本的增加。
这违反了C++的设计哲学,“不为不必要的功能支付代价”,强迫用户接受垃圾回收的代价并不可取。
相反,按需选择你自己需要的垃圾回收器,需要掌握的规则与显式的管理内存相比,简单的多,也不容易出错。
最关键的一点, C++并不是“傻瓜型”的编程语言,他青睐喜欢和善于思考的编程者,设计一个合适自己需要的垃圾回收器,正是对喜爱C++的程序员的一种挑战。
相信用过C++的程序员知道,C++的很多特性主要就是解决C语言中的各种不完美和缺陷:(注:C89、C99中许多的改进正是从C++中所引进的)1、用namespace解决了很C中变量与函数重名的问题;2...
来自: Hopefully Sky的博客
1.引用计数算法 引用计数(ReferenceCounting)算法是每个对象计算指向它的指针的数量,当有一个指针指向自己时计数值加1;当删除一个指向自己的指针时,计数值减1,如果计数值...
来自: wintree的专栏
前言垃圾收集器是一种动态存储分配器,它自动释放程序不再需要的已分配的块,这些块也称为垃圾。在程序员看来,垃圾就是不再被引用的对象。自动回收垃圾的过程则称为垃圾收集(garbagecollection)...
来自: 不很正派的专栏
C++内存对象大会战文章作者:摘自: 文章来源:天极网 发布时间:2005-07-0800:03:40 如果一个人自称为程序高手,却对内存一无所知,那么我可以告诉你,他一定在吹牛。用C或C+...
来自: zhiyang202_____________________________________________________________________
前段时间看了一些关于GC的论文、书和源码。源码指的是Boehm的保守GC ,论文也主要是围绕这个GC相关的算法,另外还包括一些survey和性能分析的论文。而其他关于GC的一些东西主要是从其他两本书上
许多C或者C++程序员对垃圾回收嗤之以鼻,认为垃圾回收肯定比自己来管理动态内存要低效,而且在回收的时候一定会让程序停顿在那里,而如果自己控制内存管理的话,分配和释放时间都是稳定的,不会导致程序停顿。最...
来自: SS33SSS的博客
#include&lt;iostream&gt;#include&lt;memory&gt;//forunique_ptr、shared_ptr、weak_ptr//智...
来自: caychen的博客
参考http://www.2cto.com/kf/201110/108419.htmlhttp://www.cnblogs.com/nele/p/5673215.html1.引用计数算法引用计数(Re...
来自: master-dragon的专栏
英文原文:MaoniStephens,编译:赵玉开(@玉开Sir)原文地址:http://blog.jobbole.com/31459/CLR垃圾回收器根据所占空间大小划分对象。大对象和小对象的处理方...
来自: 衣舞晨风
一位大神的文章,详细讲解android内存管理的,在此转载一下。http://www.cnblogs.com/killmyday/archive/2013/06/12/3132518.html一般来说...
来自: xiadidi的专栏
1、C++的内存回收机制当我们使用new为一个对象分配了空间之后,在这个对象结束使用之后,我们必须人为的去释放这块内存(delete)。2、Java的内存回收机制如下边一段代码: publicvoid...
来自: leo115的专栏
以下哪项陈述是正确的(E)A.垃圾回收线程的优先级很高,以保证不再使用的内存将被及时回收B.垃圾收集允许程序开发者明确指定释放哪一个对象C.垃圾回收机制保证了JAVA程序不会出现内存溢出D.进入"De...
来自: ﹎厡.唻
在开发.NET程序过程中,由于CLR中的垃圾回收(garbagecollection)机制会管理已分配的对象,所以程序员就可以不用关注对象什么时候释放内存空间了。但是,了解垃圾回收机制还是很有必要的,...
来自: weixin_33889245的博客
技术点来自javase的java引用类型,jvm中垃圾回收的远离。jdk1.0版本:引用的集成体系: Reference ...
来自: fengchao2016的博客
被好友问到这个问题,突然发现自己知之甚少JavaScript具有自动垃圾收集机制,为此垃圾收集器会按照固定的时间间隔,周期性地执行这一操作。垃圾收集器有两种方法:标记清除和引用计数标记清除的简单原理:...
来自: tcap99的博客
这个文章与Oracle的HotspotJVM虚拟机的内存管理为参照的。这些建议是我们在对大的Confluence安装实例用户进行咨询服务的时候得到的最佳配置方案。请不要在Confluence中使用Co...
来自: HONEY MOOSE
非托管C++C++有垃圾收集,采用Hans-BoehmGarbageCollector的形式。也可能有其他垃圾收集库。您可以使用使用RAII的智能指针(如果指针允许共享访问,则使用引用计数)来确定何时...
来自: fengmao31的博客
昨天跟同事讨论问题时提起了内存泄露,想到了以前考scjp时碰到的垃圾回收的题目,觉得挺有意思,就去查了一些有关javascript垃圾回收的资料,贴出来跟大家分享一下。垃圾回收,简单的说就是收回某些无...
说来也许不能相信,自己之前并没有在软件开发中大规模引入智能指针,担心效率啊,在vector里面塞满了shared_ptr,一旦遍历,想想都心痛。如果给有10万元素的vector和list中,随机删除和...
来自: SalmonRun的专栏
转载地址:http://blog.csdn.net/winux/article/details/1768777 转载内容的gc原理是:把所有已new的对象的信息放到一个set里,把所有smartpt...
来自: sunny_ss12的专栏
本文已经迁移到: http://cpp.winxgui.com/cn:garbage-collection-in-cplusplus使用智能指针(smartpointers)http://www.co...
来自: Jack Wang
很多系统都有其自身的垃圾回收,其回收机制大体是相同的。它们使程序员从跟踪内存使用的繁重任务中解脱出来。虽然大多数回收器都要求应用程序不时地暂停从而释放不再使用的内存。但C#中的回收器效率还是很高的。一...
来自: u014532992的专栏
对于垃圾JVM的垃圾回收机制这里我们称为GC,众所周知,java语言不需要像c++那样需要自己申请内存,自己释放内存,这些都是JVM帮我们做好了的,但是对于一名java程序员,想要更近自己的水平更上一...
来自: 纵有疾风起,人生不言弃
人们似乎认为编写垃圾回收机制是很难的,是一种只有少数智者和HansBoehm(etal)才能理解的高深魔法。我认为编写垃圾回收最难的地方就是内存分配,这和阅读K&R所写的malloc样例难度是相当的。...
来自: 西红柿炒土豆
这里介绍4个垃圾收集器,如果进行了错误的选择将会大大的影响程序的性能。 时至今日,仍然有两个事情困扰着开发人员:垃圾收集(GC)和了解异性(程序猿的悲鸣),后者我确实不太了解,因为我被前者搞的无暇顾...
来自: lijunwyf的专栏
JAVA运行时内存区域 一,垃圾收集算法GC管理的主区域是Java的堆,一般情况下只针对堆进行垃圾回收。方法区,栈,本地方法区不被GC所管理,因而选择这些区域内的对象作为GC根,被GC根引用的...
来自: qq_35649135的博客
本系列文章,主要是学习c++内存管理这一块的学习笔记。时间:6.7-21之下以技术内幕的开头语,带入到学习C++内存管理的技术中吧:内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,因此...
来自: 愚蠢的小根儿
6、CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现,它的运作过程如下:1)初始标记2)并发标记3)重新标记4)并发清除 初始标记、从新标记这两个步...
来自: linhao
目录:Sec1CC98的显式内存管理的不足Sec2C11的智能指针Sec3垃圾回收机制Sec1.C/C++98的显式内存管理的不足内存管理和垃圾回收是高级语言的重要特征,尤其是随着代码抽象程度的加深,...
来自: 墨篙和小奶猫
原文地址:点击打开链接一、本节内容本节内容包括:对标准库的扩充:智能指针和引用计数RAII与引用计数std::shared_ptrstd::unique_ptrstd::weak_ptr二、RAII与...
来自: jigetage的专栏
我们知道Java有垃圾回收(GabageCollector)机制,但c++没有这样的机制,这导致C++中对动态存储的管理成为程序员的噩梦,不是吗?c++程序员经常遇到的是内存泄露(memoryleak...
来自: 点滴的岁月
Java的爱好者们经常批评C++中没有提供与Java类似的垃圾回收(GabageCollector)机制(这很正常,正如C++的爱好者有时也攻击Java没有这个没有那个,或者这个不行那个不够好),导致...
来自: KFPA的博客
我们知道c++程序设计里面的内存分配有三种方法:1.即全局变量和静态变量,这些都是存储在静态存储区,生存的周期就是程序运行的周期当程序结束,才释放这些存储空间。2.函数内部的局部变量,这些都是在栈上分...
来自: 点滴的岁月
c#和java中有自动垃圾回收机制,.net运行时和java虚拟机可以管理分配的堆内存,在对象失去引用时自动回收,因此在c#和jva中, 内存管理不是大问题。c++语言没有垃圾回收机制,必须自己去释放...
来自: Polaroid2007的专栏
见:https://github.com/weolar/blinkgcblinkgc是一个有趣的组件,从blink/chromium里抽离出来。blinkgc能让C++拥有java一样的垃圾回收机制,...
来自: weolar的专栏
当出现循环引用的情况下,使用计数方式的智能指针无法自动释放对象。那么怎样才能释放这样的对象,或者说设计的时候就不应该出现循环引用?如果说不应该出现循环引用的话,那么在一个足够复杂的系统中,如何才能避免
[过游戏保护]之第一讲[背包、怪物...
koesmetcal:找传奇SF背包数组,通过数量入口,根本不能定位到,基本两次数量变化后就没有可用的数据了;通过不存在为0的理念,也没有能找到背包数组的地址,倒是找到了鼠标上次操作过的位置;我通过内挂里读出来的物品ID信息,倒是找到了背包,但是发现居然是不实时更新的,只有我退出重新载入游戏,这些背包内存信息才会更新,网上说的那些找背包的方法似乎都不能凑效
汇编常用指令对标志位的影响(转)
qq_32412809:很全面
判断指定的进程或程序是否存在方法
maya8maya85:转的真差,函数根本不能用。