在没有Malloc / New或Free / Delete的情况下管理连续的内存块

时间:2014-10-16 04:20:04

标签: c++ memory contiguous

如何在没有C ++中其他内存管理器(如Malloc / New)的帮助下,如何创建自定义MemoryManager来管理给定的连续内存块?

这里有更多背景信息:

   MemManager::MemManager(void* memory, unsigned char totalsize)
   {
       Memory = memory;
       MemSize = totalsize;
   }

我需要能够使用MemManager分配和释放这个连续内存的块。构造函数以字节为单位给出块的总大小。

Allocate函数应该接受以字节为单位的内存量,并返回指向该内存块开头的指针。如果没有剩余内存,则返回NULL指针。

Deallocate函数应该接收指向必须释放的内存块的指针,并将其返回给MemManager以备将来使用。

请注意以下约束:

- 除了给它的内存块,MemManager不能使用任何动态内存

- 最初指定,MemManager不能使用其他内存管理器来执行其功能,包括new / malloc和delete / free

我已经在几次面试中收到了这个问题,但即使是几个小时的在线研究也没有帮助我,我每次都失败了。我发现了类似的实现,但它们都使用了malloc / new,或者是操作系统中的通用和请求内存,我不允许这样做。

请注意,我很乐意使用malloc / new和free / delete,并且使用它们时没什么问题。

我尝试过以LinkedList方式利用节点对象的实现,这些实现指向分配的内存块并说明使用了多少字节。然而,在这些实现中,我总是被迫在堆栈中创建新节点并将它们插入到列表中,但是一旦它们超出范围,整个程序就会崩溃,因为地址和内存大小都会丢失。

如果有人对如何实现这样的事情有某种想法,我将非常感激。提前谢谢!

编辑:我忘了在原帖中直接指定这个,但是用这个MemManager分配的对象可以是不同的大小。

编辑2:我最终使用了同源内存块,由于下面的答案提供的信息,实际上很容易实现。没有指定有关实现本身的确切规则,因此我将每个块分成8个字节。如果用户请求超过8个字节,我将无法提供,但如果用户请求少于8个字节(但是> 0),那么我将提供额外的内存。如果传入的内存量不能被8整除,那么最后就会浪费内存,我认为这比使用更多的内存要好得多。

2 个答案:

答案 0 :(得分:2)

  

我尝试过利用LinkedList中的节点对象的实现   时尚,指向分配的内存块和状态多少   使用了字节。然而,通过这些实现我总是   被迫在堆栈上创建新节点并将其插入到堆栈中   列表,但一旦超出范围,整个程序就会崩溃   因为地址和内存大小都丢失了。

你走在正确的轨道上。您可以将LinkedList节点嵌入到您使用reinterpret_cast<>给出的内存块中。由于您不允许在内存管理器中存储变量,只要您不动态分配内存,就可以使用成员变量跟踪列表的头部。您可能需要特别注意对象大小(所有对象的大小是否相同?对象大小是否大于链接列表节点的大小?)

假设前面问题的答案为真,那么您可以使用跟踪自由节点的帮助程序链接列表处理内存块并将其拆分为较小的对象大小的块。您的自由节点结构将类似于

struct FreeListNode
{
    FreeListNode* Next;
};

分配时,您只需从空闲列表中删除头节点并将其返回。解除分配只是将释放的内存块插入空闲列表。拆分内存块只是一个循环:

// static_cast only needed if constructor takes a void pointer; can't perform pointer arithmetic on void*
char* memoryEnd = static_cast<char*>(memory) + totalSize; 
for (char* blockStart = block; blockStart < memoryEnd; blockStart += objectSize)
{
    FreeListNode* freeNode = reinterpret_cast<FreeListNode*>(blockStart);
    freeNode->Next = freeListHead;
    freeListHead = freeNode;
}

正如您所提到的,Allocate函数接受对象大小,需要修改上述内容以存储元数据。您可以通过在空闲列表节点数据中包含空闲块的大小来完成此操作。这消除了拆分初始块的需要,但在Allocate()和Deallocate()中引入了复杂性。您还需要担心内存碎片问题,因为如果您没有足够的内存来存储所请求的数量的空闲块,那么除了分配失败之外,您无能为力。一些Allocate()算法可能是:

1)只需返回足够大的第一个可用块来保存请求,根据需要更新空闲块。在搜索空闲列表方面,这是O(n),但可能不需要搜索大量空闲块,并可能导致碎片问题。

2)在空闲列表中搜索空闲量最小的块以保存内存。在搜索空闲列表方面,这仍然是O(n),因为你必须查看每个节点以找到最少浪费的节点,但可以帮助延迟碎片问题。

无论哪种方式,对于可变大小,您还必须在某处存储分配的元数据。如果您根本无法动态分配,那么最佳位置是在用户请求阻止之前或之后;如果要添加初始化为已知值的填充并检查填充的差异,可以在Deallocate()期间添加检测缓冲区溢出/下溢的功能。如果你想处理它,你也可以添加另一个答案中提到的紧凑步骤。

最后要注意的是:在向FreeListNode帮助器结构添加元数据时,您必须要小心,因为允许的最小空闲块大小是sizeof(FreeListNode)。这是因为您将元数据存储在空闲内存块本身中。您发现自己需要为内部目的存储的元数据越多,内存管理器就越浪费。

答案 1 :(得分:2)

管理内存时,通常需要使用您管理的内存来存储所需的任何元数据。如果你看一下malloc的任何实现(ptmalloc,phkmalloc,tcmalloc等等),你会发现这是他们通常实现的方式(当然忽略任何静态数据)。由于不同的原因,算法和结构非常不同,但我会尝试对通用内存管理的内容进行一些了解。

管理同类内存块与管理非同类块不同,它可以简单得多。一个例子......

MemoryManager::MemoryManager() {

  this->map = std::bitset<count>();
  this->mem = malloc(size * count);

  for (int i = 0; i < count; i++)
    this->map.set(i);
}

分配是找到std::bitset中的下一位(编译器可能优化),将块标记为已分配并返回它的问题。取消分配只需要计算索引,并标记为未分配。 free list是另一种方式(here描述了什么),但内存效率稍差,可能不会很好地使用CPU缓存。

免费列表可以作为管理非同质内存块的基础。有了这个,你需要存储块的大小,以及内存块中的下一个指针。大小允许您将较大的块拆分为较小的块。这通常会导致碎片,因为合并块是非平凡的。这就是为什么大多数数据结构保留相同大小的块的列表,并尝试尽可能地映射请求。