我在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);
或者你能推荐一个更好的方法吗?
答案 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
可能不是您的最佳选择。