替换默认的 STL 分配器

Posted

技术标签:

【中文标题】替换默认的 STL 分配器【英文标题】:Replace default STL allocator 【发布时间】:2011-11-23 18:53:27 【问题描述】:

我有一个大量使用 STL 容器和字符串的大型(>250 个文件)库的源代码。我需要在有限堆的嵌入式环境中运行它,所以我想确保这个库本身的堆使用受到限制。

显而易见的解决方案是创建一个分配器,但是修改整个代码库以包含分配器模板参数是最后的一项大工作,如果我想要获取新版本的源代码,这也是不可取的。全局替换 new 和 delete 是不可行的,因为这会影响整个图像,而不仅仅是这个库。

我的下一个想法是一个愚蠢的 C 宏技巧,但这似乎是不可能的,尽管我承认自己不是一个聪明的宏作者。

所以我想“是否有编译器或编译指示开关在编译时指定分配器 类”?但我愿意接受任何事情。

如果有人能提出解决方案,我要问的下一个问题是,如何在包含此库的文件集中对 new/delete 执行相同的操作。

我正在使用 gcc 3.4.4 工具链在 Cygwin 下运行它,目标是 VxWorks,如果这能激发任何想法的话。

【问题讨论】:

gcc 3.4.4 这已经够老了,可以贴上“完全损坏”的标签。你有什么理由不切换到新版本? 我认为在 GCC 中这只是内部某个地方的一个简单宏,您应该可以切换它。默认是“新分配器”,但 GCC 提供了几个替代方案,例如“malloc 分配器”和池分配器等等。 假设您可以创建一个有限堆,然后仅为该库重载operator new,因此库中的所有operator new 调用都转到您的有限堆。当有限的堆已满并调用重载的operator new 时,您想要什么行为?你真的希望operator new 在这种情况下失败吗?我不相信你会,除非当operator new 失败时该库仍然可以运行。也许如果库是数据库缓存或类似的东西。图书馆是做什么的? 我们这样做的方式是创建一个模板类来包装我们使用的每个容器。看看我发布的一个问题:***.com/q/8248076/1037208。它为您节省了一些麻烦,并使您更难犯错误。我对我们的解决方案并不完全满意,所以如果有人用更好的解决方案回答您的问题,我可能会考虑修改我们的解决方案。 @sehe,3.4.4 的原因是我们的目标系统是运行 VxWorks 的卫星硬件,而该工具链仍在 3.4.4 上。我不知道“为什么”,只是要求。 【参考方案1】:

我求助于预处理器来获得一个可能的解决方案,尽管它目前依赖于 GCC 3.4.4 实现来工作。

GCC <memory> 实现包括文件<bits/allocator.h>,该文件又包括另一个文件<bits/c++allocator.h>,该文件定义了一个宏,该宏定义了实现默认分配器基类的类。

由于在依赖于平台的路径中找到(/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/i686-pc-cygwin/bits),我不觉得(非常)用我自己的“依赖于平台”的实现来代替它。

所以我只需在源包含路径的根目录中创建一个文件夹bits/,然后在该文件夹中创建文件c++allocator.h。我将所需的宏定义为我的分配器类的名称,它就像一个魅力,因为 gcc 在搜索系统包含之前搜索我的包含路径。

感谢您的所有回复。我想我可以使用这个“解决方案”,只要我可能使用 3.4.4,它就会起作用。

【讨论】:

对我如何在 Visual Studio 中执行此操作有任何想法吗?我试图找到默认分配器头文件,但迷路了 :) 谢谢!【参考方案2】:

您可以从使用 EASTL(Enterprise Arts STL(部分)实施)中受益

EASTL -- Electronic Arts Standard Template Library

这适用于嵌入式/游戏开发,在 global heap is really scarce, non-existent or problematic in general 的环境中。

EASTL 的分配器模型的灵感来自(或类似?)著名的 Towards a Better Allocator Model 出版物 (PDF) 中的想法。

EASTL 非常适合自定义分配器。 事实上,它不附带分配器,因此需要提供(最小的)分配器,甚至可以让您的应用程序链接。

这里是 EASTL 的 github 仓库:https://github.com/electronicarts/EASTL

【讨论】:

这是一个可能的解决方案,尽管由于 std 与 eastl 命名空间,我必须编辑 EASTL 或我的库。【参考方案3】:

所以我想“是否有编译器或编译指示开关来指定 allocator class at compile time”?但我愿意接受任何事情。

不,没有。

看看here。

分配器是每个 stl 容器中的模板参数。您将需要更改它们。我过去在嵌入式工作时也做过同样的事情。如果你愿意,我可以给你一些建议:

基本模板分配器:

namespace PFM_MEM 
    template <class T>
    class CTestInstAllocator 
    public:
        // type definitions
        typedef size_t    size_type;
        typedef ptrdiff_t difference_type;
        typedef T*        pointer;
        typedef const T*  const_pointer;
        typedef T&        reference;
        typedef const T&  const_reference;
        typedef T         value_type;

        // rebind CTestInstAllocator to type U
        template <class U>
        struct rebind 
            typedef CTestInstAllocator<U> other;
        ;

        // return address of values
        pointer address (reference value) const 
            return &value;
        
        const_pointer address (const_reference value) const 
            return &value;
        

        /* constructors and destructor
        * - nothing to do because the CTestInstAllocator has no state
        */
        CTestInstAllocator() 
        
        CTestInstAllocator(const CTestInstAllocator&) 
        
        template <class U>
        CTestInstAllocator (const CTestInstAllocator<U>&) 
        
        ~CTestInstAllocator() 
        

        // return maximum number of elements that can be allocated
        size_type max_size () const 
            return std::numeric_limits<size_t>::max() / sizeof(T);
        

        // pvAllocate but don't initialize num elements of type T by using our own memory manager
        pointer allocate (size_type num) 
            /**
            * pvAllocate memory custom memory allocation scheme
            */
            return(pointer)(CPfmTestInstMemManager::pvAllocate(num*sizeof(T)));
        
        // initialize elements of allocated storage p with value value
        void construct (pointer p, const T& value) 
            // initialize memory with placement new
            new((void*)p)T(value);
        

        // destroy elements of initialized storage p
        void destroy (pointer p) 
            // destroy objects by calling their destructor
            p->~T();
        
        // vDeallocate storage p of deleted elements
        void deallocate (pointer p, size_type num) 
            /**
            *Deallocate memory with custom memory deallocation scheme
            */
            CPfmTestInstMemManager::vDeallocate((void*)p);
        
    ;

    // return that all specializations of this CTestInstAllocator are interchangeable
    template <class T1, class T2>
    bool operator== (const CTestInstAllocator<T1>&,
        const CTestInstAllocator<T2>&) 
            return true;
    
    template <class T1, class T2>
    bool operator!= (const CTestInstAllocator<T1>&,
        const CTestInstAllocator<T2>&) 
            return false;
    

请特别注意以下几行:

/**
* pvAllocate memory custom memory allocation scheme
*/
return(pointer)(CPfmTestInstMemManager::pvAllocate(num*sizeof(T)));

// vDeallocate storage p of deleted elements
void deallocate (pointer p, size_type num) 
/**
*Deallocate memory with custom memory deallocation scheme
*/
CPfmTestInstMemManager::vDeallocate((void*)p);

这里是你调用 你的 new 并删除在你的堆上工作的地方。

我可以为您提供一个示例,说明如何构建一些基本的内存管理器以进一步帮助您。

【讨论】:

所以你说没有办法告诉 STL 全局使用自定义分配器;您必须确保在使用 stl 类型时,使用自定义分配器定义它。对吗? @OrenS。好吧,对于特定的编译器,可能有一个选项,但我是根据我的经验单独谈论的。我和 OP 在同一个地方,不得不这样做。编辑更正的答案。 @FailedDev,我在发帖之前看到了那个页面,并充满希望。如果我能弄清楚如何使我的任意分配器成为“扩展”分配器,我会很高兴。我只是不知道该怎么做。如果你知道的话,我会给我的下一只狗命名为“FailedDev”...... 这个话题和c++ 17有什么变化吗?现在这里是替换全局分配器的常用方法吗?

以上是关于替换默认的 STL 分配器的主要内容,如果未能解决你的问题,请参考以下文章

SGI-STL简记-内存分配器解析

C++ STL 问题:分配器

STL容器自定义内存分配器

STL容器自定义内存分配器

STL容器自定义内存分配器

STL-空间配置器迭代器traits编程技巧