C 中用于内存管理器的各种长度结构?

Various length structure in C for memory manager?

我练习用C实现一个内存管理器

我想要具有各种长度和自我描述的结构。 所以,我偷看了一本POSIX教科书的东西,像这样:

struct layout
{
 uint32_t  size; // array size in bytes, include space after the struct
 uchar_t   data[1];
};

// But, is next line correct?
layout *val = malloc (array_memory_in_bytes + sizeof (uint32_t) - 1);
// Where does a static array keep the pointer for using it? 

如果我在不间断的内存中一个接一个地拥有多个这样的结构,并且我希望能够遍历它们。我可以写点像这样的东西吗:

layout *val1 = pointer;
layout *val2 = val1 + val1.size + sizeof (val1.size);

或者你能推荐我一个更好的方法吗?

它的标准 C 版本称为 灵活数组成员,它看起来像:

struct layout
{
    uint32_t size;
    uchar_t data[];
};

// allocate one of these blocks (in a function)
struct layout *val = malloc( sizeof *val + number_of_bytes );
val->size = number_of_bytes;

代码 val1->data + val1->size 将为您提供 space 您刚刚 malloc 的最后一个指针。

但是,您不能迭代一个 malloc' 块的末尾并希望击中另一个 malloc' 个块。要实现这个想法,您必须 malloc 一个大块,然后在其中放置各种 struct layout 对象,注意 alignment.

在这种方法中,最好还存储每个 struct layout 所在位置的索引。从理论上讲,您可以每次都从头开始浏览列表,添加 size 然后进行对齐调整;但这会很慢,而且这意味着你无法处理中间的块被释放并重新 "allocated".

如果这是 malloc 的直接替代品,那么实际上有两个对齐注意事项:

  • 对齐 struct layout
  • data 必须对齐任何可能的类型

解决这个问题的最简单方法是也为任何可能的类型对齐 struct layout。这可能看起来像(注意:需要 #include <stdint.h>):

struct layout
{
    uint64_t size;    // may as well use 64 bits since they're there
    _Alignas(max_align_t) uchar_t data[];
};

另一种方法可能是将 size 保持在 32 位并加入 pragma pack 以防止填充;那么你需要使用一些额外的复杂性来确保 struct layout 被放置在 max_align_t 字节边界之前 4 个字节,等等。我建议先用简单的方法来获取代码 运行;然后稍后您可以返回并尝试此更改,以便根据需要节省几个字节的内存。


替代方法:

  • struct layout 的每个实例及其尾随数据保存在单独的分配中。
  • data 更改为指向 malloc 的指针 space;那么您可以将所有 struct layout 对象保存在一个数组中。

一般的想法可行,但只有当 most-severe 边界对齐情况是 int 时,该特定结构才可行。

内存管理器,尤其是可能是 back-end 实现 malloc() 的内存管理器,必须知道 worst-case 边界是什么。数据的实际开始必须在该边界上,以满足分配的内存适当对齐以存储任何数据类型的一般要求。

完成此操作的最简单方法是使 layout 结构描述的长度分配 header 和实际分配大小都是该对齐单元的倍数。

无论如何,您不能将数据的开头描述为结构成员,并且该结构的大小就是 header 的大小。 C 不支持 zero-length 字段。您应该使用一些东西将该数组放在边界上,并使用 <stddef.h>.

中的 offsetof()

个人而言,我会使用 union,基于旧习惯和偶尔使用 Visual C++ for C。但是 uint32_t 是 C99 类型,如果你也有 C11 支持你可以使用 _Alignas()。这样,您的结构可能看起来像:

#define ALIGN_TYPE double /* if this is the worst-case type */
#define ALIGN_UNIT ((sizeof)(ALIGN_TYPE))
#define ALIGN_SIZE(n) (((size_t)(n) + ALIGN_UNIT - 1) & ~(ALIGN_UNIT-1))

typedef struct layout
{
    size_t size; /* or use uint32_t if you prefer */
    _Alignas(ALIGN_UNIT) char data[1];

} layout;

#define HEADER_SIZE (offsetof(layout, data))

这使得除了 worst-case 对齐类型之外的大多数内容都是符号化的。您将分配组合的 header 加数据数组:

layout *ptr = (layout*) malloc(HEADER_SIZE + ALIGN_SIZE(number_of_bytes));
ptr->size = HEADER_SIZE;

ALIGN_SIZE 类型实际上不是符号常量,除非 C99/C11 更改了 sizeof 的定义。例如,您不能使用它来计算普通数组维度。如果这是一个问题,您可以硬编码文字数字,例如 8 表示典型的双精度数字。请注意 long double 在许多 x86 实现上的大小(10 字节)有问题。如果您打算将分配单元基于类型,那么 long double 可能不是您的最佳选择。