科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件C++内存管理变革(3):另类内存管理

C++内存管理变革(3):另类内存管理

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

C++内存管理变革(3):另类内存管理

作者:许式伟 来源:CSDN 2008年1月18日

关键字: 管理 另类内存 C++ Linux

  • 评论
  • 分享微博
  • 分享邮件

最简单的C++/Java程序

最简单的Java程序:

class Program
{
   
public static void main()
   {
       
new int;
   }
}
 
对应的C++程序:
 
void main()
{
   
new int;
}
 
我想没有一个Java程序员会认为上面的Java代码存在问题。但是所有严谨的C++程序员则马上指出:上面这个C++程序有问题,它存在内存泄漏。但是我今天想和大家交流的一个观念是:这个C++程序没有什么问题。
 

DocX程序的内存管理

DocX是我开发的一个文档撰写工具。这里有关于它的一些介绍。在这一小节里,我要谈谈我在DocX中尝试的另类内存管理方法。 

DocX的总体流程是:
  1. 读入一个C++源代码(或头)文件(.h/.c/.hpp/.cpp等),分析其中的注释,提取并生成xml文档。
  2. 通过xslt变换,将xml文档转换为htm。
  3. 分析源代码中的所有include指令,取得相应的头文件路径,如果某个头文件没有分析过,跳到1反复这些步骤。
  4. 最后所有生成的htm打包生成chm文件。
一开始,我象Java/C#程序员做的那样,我的代码中所有的new均不考虑delete。当然,它一直运作得很好,直到有一天我的文档累计到了一定程度后。正如我们预见的那样,DocX程序运行崩溃了。
 
那么,怎么办呢?找到所有需要delete的地方,补上delete?
 
这其实并不需要。在前面,我给大家介绍了AutoFreeAlloc(参见《C++内存管理变革(2):最袖珍的垃圾回收器》),也许有人在嘀咕,这样一个内存分配器到底有何作用。——那么,现在你马上可以看到它的典型用法之一了:
 
对于我们的DocX崩溃后,我只是做了以下改动:
  1. 加一个全局变量:std::AutoFreeAlloc alloc;
  2. 所有的new Type(arg1, arg2, ..., argn),改为STD_NEW(alloc, Type)(arg1, arg2, ..., argn);
  3. 所有的new Type[n],改为STD_NEW_ARRAY(alloc, Type, n);
  4. 每处理完一个源代码文件时,调用一次alloc.clear();

搞定,自此之后,DocX再也没有内存泄漏,也不再有遇到内存不足而崩溃的情形。

 

只读DOM模型(或允许少量修改)的建立

在《文本分析的三种典型设计模式》一文中我推荐大家使用DOM模型去进行文件操作。并且通常情况下,这个DOM模型是只读DOM模型(或允许少量修改)。 

对于只读DOM模型,使用AutoFreeAlloc是极其方便的。整个DOM树涉及的内存统一由同一个AutoFreeAlloc实例进行分配。大体如下:
 
class Document;
class ObjectA
{
private:
    Document
* m_doc;
    SubObject
* m_c;
 
public:
    ObjectA(Document
* doc) : m_doc(doc) {
        m_c 
= STD_NEW(doc->alloc, SubObject);
    }
 
    SubObject
* getC() {
        
return m_c;
    }
};
 
class Document
{
public:
    AutoFreeAlloc alloc;
 
private:
    ObjectA
* m_a;
    ObjectB
* m_b;
 
public:
    ObjectA
* getA() {
        
if (m_a == NULL)
            m_a 
= STD_NEW(alloc, ObjectA)(this);
        
return m_a;
    }
};
 
通过这种方式创建的DOM模型,只要你删除了Document对象,整个DOM树自然就被删除了。你根本不需要担心其中有任何内存泄漏的可能。
 

另类内存管理的观念

通过以上内容,我试图向大家阐述的一个观点是:

  • 有了AutoFreeAlloc后,C++程序员也可以象GC语言的程序员一样大胆new而不需要顾忌什么时候delete。

展开来讲,可以有以下结论:

  • 如果你程序的空间复杂度为O(1),那么只new不delete是没有问题的。
  • 如果你程序的空间复杂度为O(n),并且是简单的n*O(1),那么可以用AutoFreeAlloc简化内存管理。
  • 如果你程序的空间复杂度为O(t),其中t是程序运行时间,并且你不能确定程序执行的总时间,那么AutoFreeAlloc并不直接适合你。比较典型的例子是Word、Excel等文档编辑类的程序。

 

用AutoFreeAlloc实现通用型的GC

 
AutoFreeAlloc对内存管理的环境进行了简化,这种简化环境是常见的。在此环境下,C++程序员获得了无可比拟的性能优势。当然,在一般情形下,AutoFreeAlloc并不适用。
 
那么,一个通用的半自动GC环境在C++是否可能?《C++内存管理变革》系列的核心就是要告诉你:当然可以。并且,我们推荐C++程序员使用半自动的GC,而不是Java/C# 中的那种GC。
 
通用的半自动GC环境可以有很多种建立方式。这里我们简单聊一下如何使用AutoFreeAlloc去建立。
 
我们知道,使用AutoFreeAlloc,将导致程序随着时间推移,逐步地吃掉可用的内存。假设现在已经到达我们设置的临界点,我们需要开始gc。整个过程和Java等语言的gc其实完全类似:通过一个根对象(Object* root),获得所有活动着的对象(Active Objects),将它们复制到一个新的AutoFreeAlloc中:
 
Object* gc(AutoFreeAlloc& oldAlloc, Object* root, AutoFreeAlloc& newAlloc)
{
    Object* root2 = root->clone(newAlloc);
    oldAlloc.clear();
    return root2;
}
 
如果C++象Java/C#那样有足够丰富的元信息,那么Object::clone过程就可以象Java/C# 等语言那样自动完成。这些元信息对于GC过程的用处无非在于,我们可以遍历整个活动对象的集合,然后把这些活动对象复制一份。没有复制过来的对象自然而然就被丢弃了。
 
GC的原理就是这么简单。没有元信息也没关系,只要我们要求每个由GC托管的对象支持clone函数,一切就ok了。对于一个复杂程序,要求每个对象提供clone函数不见得是什么过分的要求,clone函数也不只有gc过程才需要,很多对象在设计上天然就需要clone。
 
 

补充说明

关于全局AutoFreeAlloc变量

我个人非常不推荐使用全局变量(除非是常量:不一定用const修饰,指的是经过一定初始化步骤后就不在修改的变量)。上面只是对于小型的单线程程序偷懒才这样做。
 

关于用AutoFreeAlloc实现通用型的GC

请注意我没有讨论过于细节的东西。如果你决定选择这种做法,请仔细推敲细节。可以预见的一些细节有:

  • AutoFreeAlloc与线程模型(ThreadModel)。AutoFreeAlloc关注点在于快,它通常不涉及跨线程问题。但是如果要作为通用型的GC,这一点不能不考虑。为了性能,推荐每个线程独立管理内存,而不要使用互斥体。
     
  • 性能优化。可以考虑象Java的GC那样,使用两个AutoFreeAlloc,把对象划分为年轻代和年老代。

 

查看本文来源
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章