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

时间:2015-03-11 21:35:55

标签: c memory-management struct

我在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);

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

2 个答案:

答案 0 :(得分:4)

此标准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会在malloc'之后的空间中找到一个指针。

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

在这种方法中,最好还存储每个struct layout所在位置的索引。从理论上讲,您可以从一开始就浏览列表,添加size然后进行对齐调整;但这样做会很慢,也就意味着你无法应对中间被释放和重新“分配”的障碍。

如果这是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'空格的指针;那么你可以将所有struct layout个对象保存在一个数组中。

答案 1 :(得分:2)

一般的想法是可行的,但是只有在最严重的边界对齐情况是int时,该特定结构才会起作用。

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

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

无论如何,您都不能将数据的开头描述为结构成员,并且该结构的大小应该是标题的大小。 C不支持零长度字段。您应该使用某些东西将该数组放在边界上,并使用offsetof()中的<stddef.h>宏。

就个人而言,我根据旧习惯和偶尔使用Visual C ++来使用union但是uint32_t是C99类型,如果你也有C11支持,你可以使用{{ 3}}。有了它,您的结构可能看起来像:

#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))

除了最坏情况的对齐类型之外,这使得大多数符号都是符号。您将组合标头加数据数组分配为:

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

除非C99 / C11改变了sizeof的定义,否则ALIGN_SIZE类型确实不是符号常量。例如,您无法使用计算普通数组维度。如果这是一个问题,您可以硬编码一个字面数字,例如8代表一个典型的双数字。请注意long double在许多x86实现上的大小有问题(10个字节)。如果您要将分配单元作为某种类型的基础,则long double可能不是您的最佳选择。