自定义堆内存分配器 - 分配的对象必须指向分配器?

3
在存在许多自定义堆分配器实例的环境中,以下陈述是否正确?:-
  1. Each allocated pointer should be encapsulated in a type (e.g. Ptr)
    that cache pointer of who allocated me (Allo*) e.g.

    class Ptr={     
        Allo* alloPtr;  //<-- to keep track
        public: void delete(){ 
            //call back to something like "a.deallocate()"
        }
    }
    Allo a;   //a custom allocator
    Ptr<X> x=a.allocate<X>();
    x.delete();  
    

    I think I need to do that to make delete() easier.

  2. If the allocator is more complex, e.g. internally keep a lot of chunks :-

    enter image description here

    Ptr should keep track which chunk it resides on, OR
    there must be some tiny flag before the allocated content :-

    enter image description here

    Otherwise, I have to iterate each chunk to find the address -> low performance.

对于自定义分配器我非常新手,如果这太简单了,那我很抱歉。这不是作业 - 我正在尝试改进我的游戏库。

我已经阅读了:


两者都不正确。 - user3344003
@user3344003(有点害羞地问)..... 怎么做? - javaLover
对于#1,堆分配不必以那种方式编写。对于#2,堆可以跟踪已分配的块,但这并不是分配数据的一部分。 - user3344003
1个回答

1

有一些实现方式将元数据保存在正在使用的对象附近,但这些系统容易受到安全威胁。

如果您控制了缓冲区溢出或缓冲区下溢,您可以修补分配器元数据的内存,导致您的代码执行而不是分配。

我见过一种完全使用独立内存区域来存储数据和元数据的实现。这确保了可以识别溢出、下溢和错误释放。

这种系统的缺点是需要从指针中搜索元数据。

这意味着某种形式的容器需要以内存为键,并且受到与大多数容器实现相关的O(ln n)性能限制,因此在管理方面比本地数据表现得更少。

更新

原地元数据

+----------+               +-----------+           +--------------+
| pointer  |               | vtable    | ------->  | int free()   |
| to chunk | ------------->|meta data  |           S void*alloc() S
+----------+               +-----------+
|memory    |
|          |
+----------+

指向块的指针出现在返回的内存之前(或之后)。如果内存用于指针数组,则memory[-1]是有效的内存,但指向元数据。如果我可以说服代码从外部写入值,那么我就控制了程序,因为它将寻找我控制的虚表。每个分配的指针都应该封装在一个类型中(例如Ptr),该类型缓存分配我的指针(Allo*)。因此,不一定每个项目都有自己的元数据。为了跟踪分配情况,重要的是要了解使用了哪种方案。在现代编程中,对于小项(<10个字节)的分配与处理大项(>500个字节)的分配不同。小项可以从数组中的插槽进行分配,仅为块保留元数据。较大的大小不需要相同数量的批量处理,因为开销的相对成本降低了。我希望小内存块被批量处理,而较大的项目具有单独的元数据。

Ptr应该跟踪它所在的块,或者在分配的内容之前必须有一些微小的标志:-

具体来说,需要一种方法来找到用于分配的内存分配方案,并且可以通过将元数据与分配一起存储或使用一些外部方案来查找内存(例如)来完成。

std::map<void*, MetaData> mMetadataChunk;

请注意使用std::map进行分配的危险,每个跟踪项会有两个内存分配。
还存在简单的分配方案,为特定类添加(线程安全的)链表,可以轻松地重复使用给定对象的内存。
 class FrequentAllocateDestroy {
      ...
      static SomeListType mSpare;
      void* operator new  ( std::size_t count ) {
          // lock 
          if (mSpare.empty ) {
              return ::new( count );
          } else {
              void * mem = mSpare.top();
              mSpare.pop();
              return mem;
          }
      }
      void operator delete( void * mem){
           // lock
           mSpare.push( mem );
      }
 } 

关于第二段:1. 你是指 https://en.wikipedia.org/wiki/Buffer_underrun 吗?还是指太低或太高的地址(即索引越界)?...2. 另一个问题,这只是元数据。它如何“导致代码执行”?这是一件坏事吗? - javaLover
如果我将元数据存储在allocate()返回的地址附近,我的自定义分配器将导致安全漏洞。如果用户不够小心(例如访问[-1]),一切都会崩溃,而且很难调试。 <-- 这是您的意思吗? - javaLover

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接