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

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

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等。在下一篇文章中将会介绍几种经典的垃圾回收算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值