当前在线人数15893
首页 - 分类讨论区 - 电脑网络 - 葵花宝典版 -阅读文章
未名交友
[更多]
[更多]
文章阅读:关于内存泄漏
[同主题阅读] [版面: 葵花宝典] [作者:rebatezhq] , 2005年08月25日17:15:07
rebatezhq
进入未名形象秀
我的博客
[上篇] [下篇] [同主题上篇] [同主题下篇]

发信人: rebatezhq (test), 信区: Programming
标  题: 关于内存泄漏
发信站: BBS 未名空间站 (Thu Aug 25 17:16:57 2005), 转信

前几天我在这说可以用非常简单的方法解决内存泄漏的问题,很多人都不相信。
现在有几小时空闲,码几行字加以说明。首先声明以下讨论仅限C++。

首先我们把需求列一下:

1.管理一片内存或需要分配的资源,要求程序员可以完全只分配,不释放,系统
  将自动在该内存或资源不再需要的时候释放。
2.程序员可以清楚地知道系统究竟在什么地方释放的资源。这样程序员就可以对
  整个系统有完全的控制。
3.所使用的方法的书写复杂度与不用该方法时差不多。例如:用new/delete只写
  2-3行的,用新方法最多3-4行。
4.在大多数C++编译器下面所使用的方法不能有歧义。
5.当管理内存时,可以把内存按数组访问,并且可以检测内存越界错误。同样程
  序书写复杂度也不能增加。

我们再来看看C++语言本身的特性。设计C++的时候必须有一个计算机的模型,这
个模型中有的特性C++就必须支持,而没有的特性就不会出现在C++中。大多数程
序语言都是基于单带图灵机设计的,C++也不例外。在单带图灵机中,是没有线
程的概念的,所以C++也没有线程的概念。所以我们现在不考虑多线程的问题。
这并不意味着这里提出的方法无法在多线程程序中使用。我们将在后面讨论所使
用的方法对多线程程序的影响。

既然不考虑多线程,那么C++就是串行顺序执行的。C++的基本执行单元是语句。
函数体是一个复合语句。里面每行可以是简单语句,也可以多行合成一个复合语
句。由于简单语句功能过于简单,我们的讨论一般不涉及,所以后面将用“语句”
指复合语句。

数据必须要被语句所使用才有意义。一个数据通常仅被少数几个语句使用。在这
几个语句以外,该数据是否存在对程序是没有影响的。所以从C开始我们就有了
变量的作用域的概念。当程序进入某变量的作用域后分配变量所须资源,而离开
其作用域后释放其资源。对简单变量来说,我们一般是在栈上分配其所需资源的。
编译器自动维护在栈上分配的变量,并不需要程序员管理。这样的变量总是在我
们需要的时候已经被分配好,而在用完的时候被自动释放。我们都已经习惯于这
些语法,从来没有觉得要去释放这些变量的内存,也不可能因此导致内存泄漏。

现在让我们再来看看用new分配的内存块在使用上与上面这些简单变量有什么区
别。事实上,如果不考虑分配和释放,这些内存块和简单变量在使用上基本上没
有区别。如果你不信,你可以去查看你自己的程序,90%以上的情况,这些内存
块都可以看成是块头比较大的变量而已。如果我们假设栈的空间是无限的,我们

再修改C++语法允许在栈上定义运行时才能决定大小的数组,那么这些内存块完
全可以在栈上分配,并且不需要程序员去释放:编译器会在离开其作用域时自动
释放的。

现在的问题是另外10%.我们可以返回一个简单变量,但一般不能返回一大块内存。
如果所有内存块都在栈上分配,则返回指针也是不行的,因为在返回前这些内存
已经被释放。可行的办法是在上一级函数分配内存,然后把指针传下去。其实简
单变量也有这样的用发:当需要返回多个简单变量的时候,除第一个外的都要由
上一级函数定义,再把引用传下去。这事实上是在上一级函数分配内存。另一个
问题是可能上一级函数不知道要多少内存。如果按最大可能分配,一方面是浪费,
另一方面是有时候不现实。在我们的解决方法中,这将不成为问题。

另外可能的问题是几个函数都需要访问同一个内存块。这种情况可以把这个内存
块定义为类变量,而这个类的实例最终仍然可以是某个函数的局部变量。如此我
们仍然不需要去释放这个内存块,编译器会自动完成。事实上简单变量也有如此
用法。

综上所述,如果栈无限大,而C++语法允许(在栈上)定义运行时确定大小的数组
和类变量的话,我们根本不需要delete。编译器自己就可以适当地完成内存分配

和释放工作。这样也就不存在内存泄漏的问题。

但是现实中可无限扩展的栈成本太高(主要是考虑到多线程的问题),不符合C++
的设计目标。所以我们仍然需要堆,需要考虑delete,需要考虑内存泄漏。

我相信到现在大部分的人已经明白该如何做了。我码上面那么多文字,无非想说
明为什么这么简单的方法管用。为了完整性我先把代码样例写出来:

template<class T>
TArray
{
public:
    T  *img;
    int len;

    TArray()
    {
        img = NULL;
        len = 0;
    }

    ~TArray()
    {
        Close();
    }

    void Close()
    {
        if(img)
        {
            delete []img;
        }

        img = NULL;
        len = 0;
    }

    void Alloc(int n)
    {
        Close();
        img = new T[n];
        len = n;
    }

    T &operator[](int i)
    {
        assert(img);
        assert(i>=0);
        assert(i<len);

        return img[i];
    }

    TArray<T> &operator=(TArray<T> &o)
    {
        ...
    }
};

这只是最基本的样例。每个人可以根据自己需要加不同的功能。对其它资源如文
件、窗口都可以类似处理,而不用耽心泄漏问题。可能很多人还没有注意到除了
内存还有别的东西可以泄漏吧。至于为何可以实现开始列出的5点要求,我不准备
一一解释,因为这实在需要太多文字。

当然,用这里的方法要求书写程序时对书写风格稍作改变。如果你仍然到处定义
指针并用new,这个模板不可能发挥任何作用。

我相信有人会说在大工程/跨module/function/thread...什么的有问题。我只想
说,这话和没说也没区别。这里只用了C++的基本语法,和多大的工程,跨多少
module/function...没有任何关系。至于线程,那不是C++的概念,是程序员自己
应该考虑的问题。事实上,我重载了赋值运算,这对大多数多线程程序够用了。

我用这个方法也写了20-30万行代码。我自己有一个10万行的库,基本上我的每个
程序都要调用这个库。我的程序除库以外一般有4-6万行自己写的代码,然后再连
接别人的代码约5-15万行。我自认为这样的规模是不能叫做大工程的。不过,如果
你不能提出更好的解决方案并有差不多规模的实践检验,请闭嘴。


--

※ 来源:·BBS 未名空间站 mitbbs.com·[FROM: 152.15.]

[上篇] [下篇] [同主题上篇] [同主题下篇]
[转寄] [转贴] [回信给作者] [修改文章] [删除文章] [同主题阅读] [从此处展开] [返回版面] [快速返回] [收藏] [举报]
 
回复文章
标题:
内 容:

未名交友
将您的链接放在这儿

友情链接


 

Site Map - Contact Us - Terms and Conditions - Privacy Policy

版权所有,未名空间(mitbbs.com),since 1996