【问题标题】:Various length structure in C for memory manager?C中用于内存管理器的各种长度结构?
【发布时间】:2015-04-06 23:48:03
【问题描述】:

我练习用 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);

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

【问题讨论】:

  • 看起来相当合理,尽管您可能希望/需要在对象之间添加对齐。如果您有 C99 编译器,请使用灵活的数组成员。
  • 根据平台的不同,您可能需要考虑“大小”和“数据”之间的填充。如果可以的话,你应该明确定义结构的包装以确保没有任何额外的空间。
  • @doynax,感谢您使用“灵活数组成员”一词,我找到了问题的答案stackoverflow.com/a/247040/864113
  • @joshpoley,行'结构布局 val = malloc( sizeof *val + (number_of_bytes - 1)); / 不灵活 */' 不依赖于打包方式
  • 关于“静态数组在哪里保存使用它的指针?” ,它没有。这样的指针是根据需要创建的右值。

标签: c memory-management struct


【解决方案1】:

标准 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'd 的空间的末尾得到一个指针。

但是,您不能迭代一个 malloc'd 块的末尾并希望击中另一个 malloc'd 块。要实现这个想法,你必须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-byte 边界之前的4 个字节处,依此类推。我建议先用简单的方法来运行你的代码;然后稍后您可以返回并尝试此更改,以便根据需要节省一些内存字节。


替代方法:

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

【讨论】:

  • 如果你这样做,你必须考虑对齐:每个结构应该在 4 字节边界上对齐,以便uint32_t size 成员正确对齐。要么将大小保持为 4 的倍数,要么在结构之间添加一些额外的填充。
  • @so-olitary 问题是data 中的字节数可能不是对齐的倍数;所以你必须在放置另一个 struct layout 之前插入一些填充字节
  • @so-olitary: 使用sizeof(struct layout) 并没有真正帮助对齐,因为您添加了数组长度而不对齐它。它实际上使事情变得更糟,因为在 C99 之前的版本中,struct header 在 1 字节数组之后包含 3 字节的填充。您应该使用offsetof(struct layout, data) + align4(number_of_bytes)align4 的定义:#define align4(n) (((n)+3)&~3)。当然,您必须使用自己的分配器来创建这个伪结构数组,而不是为每个结构创建单独的malloc
  • @chqrlie 在标准 C 版本中,val1->data + align4(number_of_bytes) 更简单。您也可以使用 C11 中引入的 alignof 运算符来代替硬编码对齐 4。(平台可能对对齐要求不那么严格)
  • 另一个考虑是 malloc 被指定为返回空间对齐任何可能的类型。如果此内存管理器旨在替代malloc,那么您实际上必须做一些恶作剧才能使val1->data 正确对齐,以实现系统上任何类型的最大可能对齐。
【解决方案2】:

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

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

完成此任务的最简单方法是使layout 结构描述的长度分配标头和实际分配大小成为该对齐单元的所有倍数。

无论如何,您不能将数据的开头描述为结构成员,并且该结构的大小就是标题的大小。 C 不支持零长度字段。您应该使用一些东西将该数组放在边界上,并使用来自<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))

这使得除了最坏情况的对齐类型之外的大多数东西都是象征性的。您将使用以下方式分配组合的标头和数据数组:

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 可能不是您的最佳选择。

【讨论】:

    猜你喜欢
    • 2012-04-22
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    • 1970-01-01
    • 2012-01-24
    • 1970-01-01
    相关资源
    最近更新 更多