您的位置:首页 > 其它

垃圾回收系列(1):没有GC,世界将会怎样

2010-11-02 22:31 302 查看
最近在公司内部做了一次关于垃圾回收的讲座,我打算用几篇文章把讲座的内容整理出来,供大家参考。在开始之前,我们有必要稍微复习一下内存分配的主要方式,大多数主流语言都支持三种内存分配方式:

1. 静态分配:静态变量和全局变量的分配形式

2. 自动分配:在栈中为局部变量分配内存的方法

3. 动态分配:在堆中动态分配内存空间以存储数据的方式

如何管理堆对象的生命周期,正是我们要探讨的话题。从面向对象的角度来看,每个对象的生命周期应该由自己管理,也就是说,作为一个对象,它知道自己
什么时候被创建,什么时候被销毁。然而事实上却不是这样,因为对象之间有相互引用关系,所以对象往往不知道自己什么时候可以宣告死亡,如果对象释放太早,
会造成“悬空引用”问题;如果释放的太晚或者不释放,又会造成内存泄露问题。

在C/C++中提供了显式的内存管理方案,可以用malloc/new来显式分配一段内存,而当这段内存不再需要的时候,使用free/delete把它返回给系统,看下面这段代码:

int
main()

{

string *ptr = new
string;

// do something

delete
ptr;

}

这个过程非常的自然清晰,不是吗?在使用之前用new分配一段内存,使用完毕后再用delete销毁。可惜的是,现实世界中的代码不都是这么简单,开发人员会由于各种各样的原因,而忘记释放内存,或者释放内存不正确,从而导致内存泄露。下面是一个典型的内存泄露示例:

void
service(int
n, char
** names)

{

for
(int
i=0; i< n; i++)

{

char
* buf = (char
*) malloc(strlen(names[i]));

strncpy(buf,names[i], strlen(names[i]));

}

}

显然这里释放内存的工作应该由开发人员完成,但开发人员却忘记了调用free()。

void
service()

{

Node* x = new
Node("mid-autumn"
);

Node* ptr = x;

delete
x;

cout << ptr->data << endl;

}

这段代码的问题在于不正确的调用了delete,提前释放了x的内存而导致最后一句ptr->data出错。麻烦的事情远不止这些,继续看下面这段代码:

int
main()

{

string *ptr = new
string[100];

// do something

delete
ptr;

}

这段代码中看起来很美,使用new来为100个string对象分配内存,最后也使用了delete来销毁,但不幸的是这段代码仍然不正确,这里为
100个string对象分配的对象,最后可能有99个string未必删除,原因在于没有使用相同形式的new和delete,正确的应该为:

int
main()

{

string *ptr = new
string[100];

// do something

delete
[] ptr;

}

注意到最后那个[]了吗?简单的说,在调用new时使用了[],在调用delete时也要使用[]。但这条规则又不像我们说的这么简单,有了typedef,在调用new时可能没有使用[],但是在调用delete时却要使用[],如下面这段代码:

typedef
string address[4];

int
main()

{

string *ptr = new
address;

// do something

delete
[] ptr;

}

噩梦还没有到此结束,如果我们有两个类型Array和NamedArray,其中NamedArray公有继承于Array,如下面的代码所示:

template
<class
T>

class
Array

{

public
:

Array(int
lowBound, int
highBound);

~Array();

private
:

vector<T> data;

size_t size;

int
lBound, int
hBound;

};

template
<class
T>

class
NamedArray : public
Array<T>

{

public
:

NamedArray(int
lowBound, int
highBound, const
string& name);

private
:

string* aName;

};

开发人员在使用上面两个类型时,写了这样一段代码:

int
main()

{

NamedArray<int
> *pna = new
NamedArray<int
>(10,20,"Users"
);

Array<int
> *pa;

pa = pna;

// do something

delete
pa;

}

看出问题所在了吗?最后一行调用delete并不能释放aname所占用的内存,原因在于Array类型的析构函数~Array()并没有被声明为virtual!所以父类的析构函数并没有表现出多态的特性。

通过上面的几个例子,想必大家已经体会到了显式管理内存的困难,于是在C++里出现了智能指针的概念,来减少开发人员手工管理内存出错的可能性,最
常见的如STL中的std::auto_ptr,本质上它也是个普通的指针,只不过std::auto_ptr会在析构的时候调用 delete
操作符来自动释放所包含的对象。

int
main()

{

auto_ptr<int
> ptr(new int
(42));

cout << *ptr << endl;

// 不再需要delete了

}

在大名鼎鼎的Boost C++库中更是包含了很多的智能指针,可用于各种情况,如作用域指针boost::scoped_ptr,共享指针boost::shared_ptr等等,如下代码所示:

#include
<boost/shared_ptr.hpp>

#include
<vector>

int
main()

{

std::vector<boost::shared_ptr<int
> > v;

v.push_back(boost::shared_ptr<int
>(new int
(1)));

v.push_back(boost::shared_ptr<int
>(new int
(2)));

}

对象生命周期管理,除了使用显式管理方案之后,还有一种机制就是隐式管理,即垃圾回收(Garbage
Collection,简称为GC),最早出现于世界第二元老语言Lisp中,Jean E.
Sammet曾经说过,Lisp语言最长久的贡献之一是一个非语言特征,即代表了系统自动处理内存的方法的术语极其技术——垃圾回收
(GC,Garbage
Collection)。而现在很多平台和语言都支持垃圾回收机制,如JVM、CLR、Python等。在下一篇文章中将会介绍几种经典的垃圾回收算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: