C++的简易内存管理工具实现--内存分配

Writing a Memory Allocator

接下来介绍了我在实现内存分配管理工具的具体细节,其中包括内存分配、内存池、垃圾回收的实现。
其他文章请详见:
内存分配:添加链接描述
内存池:添加链接描述
垃圾回收:添加链接描述
项目代码:添加链接描述

Mutator、Allocator和Collector

垃圾收集程序由三个主要模块组成,即Mutator、Allocator和Collector。

Mutator是我们的用户程序,我们在其中为自己的目的创建对象。所有其他模块都应该尊重Mutator 在对象图上的视图。例如,在任何情况下,收集器都不能回收活动对象。

然而,Mutator 不会自己分配对象。相反,它将这个通用任务委托给Allocator模块——这正是我们今天主要讨论的主题。

在这里插入图片描述
让我们深入了解实现Memory Allocator的细节。
Memory block
通常我们在高级编程语言中处理类对象时会包括:structure, fields, methods, etc:
const rect = new Rectangle({width: 10, height: 20});

但是,从内存分配器的角度来看,它在较低级别工作,对象仅表示为内存块。众所周知,这个块有一定的大小,它的内容是不透明的,被视为原始字节序列。在运行时该内存块可以被浇铸到一个所需的类型,它的逻辑布局可能会因该铸造。

内存分配总是伴随着内存对齐和对象头。标头存储与每个对象相关的元信息,以及分配器和收集器用途的服务器。

我们的内存块将结合object header和payload pointer,后者指向用户数据的第一个字。这个指针在分配请求时返回给用户:

/**
 * Machine word size. Depending on the architecture,
 * can be 4 or 8 bytes.
 */
using word_t = intptr_t;
 
/**
 * Allocated block of memory. Contains the object header structure,
 * and the actual payload pointer.
 */
struct Block {
   
 
  // -------------------------------------
  // 1. Object header
 
  /**
   * Block size.
   */
  size_t size;
 
  /**
   * Whether this block is currently used.
   */
  bool used;
 
  /**
   * Next block in the list.
   */
  Block *next;
 
  // -------------------------------------
  // 2. User data
 
  /**
   * Payload pointer.
   */
  word_t data[1];
 
};

如您所见,标头跟踪size对象的 ,以及当前是否已分配此块–used标志。在分配时它被设置为true,并且在free操作时它被重置回false,因此可以在未来的请求中重用。此外,该next字段指向所有可用块的链表中的下一个块。
该data字段指向返回用户值的第一个单词。
这是块在内存中的外观图片:

在这里插入图片描述
对象A、 和C正在使用中,块B当前未使用。
由于这是一个链表,我们将跟踪堆的开始和结束:

/**
 * Heap start. Initialized on first allocation.
 */
static Block *heapStart = nullptr;
 
/**
 * Current top. Updated on each allocation.
 */
static auto top = heapStart;

Allocator interface

在分配内存时,我们不对对象的逻辑布局做出任何修改,而是直接使用块的大小。

模仿这个malloc函数,我们有以下接口(除了我们使用 typedword_t而不是void返回类型):

/**
 * Allocates a block of memory of (at least) `size` bytes.
 */
word_t *alloc(size_t size) {
   
  ...
}

Memory alignment

为了更快地访问,内存块应该对齐,通常是机器字的大小。
这是带有对象标题的对齐块的图片:
在这里插入图片描述
让我们定义对齐函数:

/**
 * Aligns the size by the machine word.
 */
inline size_t align(size_t n) {
   
  return (n + sizeof(word_t) - 1) & ~(sizeof(word_t) - 1);
}

这意味着,如果用户请求分配,比如说6 bytes,我们实际上分配了8 bytes。分配 4 个字节可能导致 4 个字节(在 32 位架构上)或 8 个字节(在 x64 机器上)。
让我们做一些测试:

// Assuming x64 architecture:
 
align(3);  //  8
align(8);  //  8
align(12); // 16
align(16); // 16
...
 
// Assuming 32-bit architecture:
align(3);  //  4
align(8);  //  8
align(12); // 12
align(16); // 16
...

所以这是我们要对分配请求做的第一步:

word_t *alloc(size_t size) {
   
  size = align(size);
  ...
}

Memory map

内存布局:
在这里插入图片描述
正如我们所看到的,堆向上增长,朝向更高的地址。而在区域之间的栈和堆是未映射区域。映射由控制位置的的程序中断(BRK)指针。
内存映射有几个系统调用:brk、sbrk和mmap。生产分配器通常使用它们的组合,但是为了简单起见,我们将仅使用sbrk调用。
具有当前堆的顶部,该sbrk函数增加程序中断在传递的字节数上的值。

以下是从操作系统请求内存的过程:

#include <unistd.h>   // for sbrk
...
 
/**
 * Returns total allocation size, reserving in addition the space for
 * the Block structure (object header + first data word).
 *
 * Since the `word_t data[1]` already allocates one word inside the Block
 * structure, we decrease it from the size request: if a user allocates
 * only one word, it's fully in the Block struct.
 */
inline size_t allocSize(size_t size) {
   
  return size + sizeof(Block) - sizeof(std::declval<Block>().data);
}
 
/**
 * Requests (maps) memory from OS.
 */
Block *requestFromOS(size_t size) {
   
  // Current heap break.
  auto block = (Block *)sbrk(0);                // (1)
 
  // OOM.
  if (sbrk(allocSize(size)) == (void *)-1) {
       // (2)
    return nullptr;
  }
 
  return block;
}

通过调用sbrk(0)-- (1),我们获得了指向当前堆中断的指针——这是新分配块的开始位置。

接下来在 (2) 中我们sbrk再次调用,但这次已经传递了我们应该增加中断位置的字节数。如果此调用结果为(void *)-1,此时说明内存已经被分配完了,则我们发出OOM(内存不足)信号,返回nullptr. 否则我们返回在(1)中获得的分配块的地址。

重申对allocSize函数的评论:除了实际请求的大小之外,我们还应该添加Block存储对象头的结构的大小。但是,由于用户数据的第一个字已经在字段中自动保留,我们减少它。

requestFromOS 只有在我们的块链表中没有可用块时,我们才会调用。否则,我们将重用一个空闲块。

在 Mac OSsbrk上已弃用,目前通过使用预先分配的内存区域进行模拟mmap。
要使用sbrk与clangMac OS上没有警告,添加:

好的,现在我们可以从操作系统请求内存:

/**
 * Allocates a block of memory of (at least) `size` bytes.
 */
word_t *alloc(size_t size) {
   
  size = align(size);
 
  auto block = requestFromOS(size);
 
  block->size = size;
  block->used = true;
 
  // Init heap.
  if (heapStart == nullptr) {
   
    heapStart = block;
  }
 
  // Chain the blocks.
  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值