我最近看到以下类似结构可以包含有关缓冲区的信息。我知道“ buffer
”字段可能指向缓冲区的起始地址,所以我们可以使用memcpy(pDst, pEntry->buffer, bufferLen)
将缓冲区复制到另一个地方(pDst
),但是我们如何分配Entry
呢?
struct Entry
{
// data fields.
size_t bufferLen;
unsigned char buffer[1];
};
这是在 C99 中转换为“灵活数组成员”的旧的 C99 之前的“struct hack”示例。假设您要存储一个字符串。您可以使用以下方法动态分配空间:
struct Entry *make_entry(const char *str)
{
size_t len = strlen(str) + 1;
struct Entry *e = malloc(sizeof(struct Entry) + len);
if (e != 0)
{
e->bufferLen = len;
strcpy(e->buffer, str);
}
return e;
}
在 C99 中,您将编写相同的代码;但是,结构的声明会有所不同,因为您没有指定数组的大小:
struct Entry
{
size_t bufferLen;
unsigned char buffer[];
};
这将使用更少的空间,尤其是在sizeof(size_t) == 8
.
此类结构(灵活的数组成员样式和 struct hack 样式)存在重大限制,其中之一是您不能简单地分配结构。您必须留出额外的空间并使用memmove()
or复制它memcpy()
。
struct Entry *dup_entry(const struct Entry *e)
{
struct Entry *r = malloc(sizeof(struct Entry) + e->bufferLen);
if (r != 0)
memmove(r, e, sizeof(struct Entry) + e->bufferLen);
return r;
}
您也不能创建此类结构的数组(但您可以创建指向此类结构的指针数组)。
我认为在您的
make_entry()
函数中,我们可以少分配一块内存,因为Entry->buffer
已经存储了一个字符。
有趣的评论——而且是轻描淡写。曾经有另一个答案(现已删除),它收集了一些有趣和中肯的评论,我将加入这个答案。
迈克尔·伯尔指出:
memcpy(pDst, pEntry, offsetof(struct Entry, buffer[pEntry->bufferLen]))
将是一个更安全的习惯用法 - 如果在结构末尾的数组破解之前碰巧有多个条目,您不必手动考虑所有这些条目,它可以让编译器处理之前的任何填充自动破解数组。同样,实例的分配可以是:pEntry = malloc(offsetof(struct Entry, buffer[desiredVarArrayElements]))
这是一个有趣的观察,并且与您的建议非常一致。这种使用offsetof()
会导致结果不是编译时常量;结果取决于运行时用作下标的值。这是非正统的,但我看不出有什么问题。用作下标的大小在计数中包含空字节至关重要。
它还导致了一个有趣的观察结果,即如果将这种技术用于短字符串,则最终为结构分配的字节数可能会比sizeof(struct Entry)
本来的少。这是因为整个结构在大多数 32 位机器上是 4 字节对齐的,而在大多数机器上是 8 字节对齐的sizeof(size_t) == 8
(通常是 64 位 Unix 系统,但不是 64 位 Windows)。
因此,如果要分配的字符串只有 3 个字节(2 个字符和 1 个空终止符字节),则指定的大小malloc()
将是 7 或 11(分别在 32 位或 64 位机器上),与结构大小相比8 或 16。
因此,我为“struct hack”结构编写的代码(如问题所示)采用了(安全)快捷方式,并过度分配了所需的总内存,可能是 4 个字节或 8 个字节。有可能减少这种过度分配。请注意,内存分配器本身通常分配向上取整的大小——通常(但不一定)在 32 位系统上为 8 字节的倍数,在 64 位系统上为 16 字节。这意味着分配更少字节的尝试可能没有您期望的那么多好处(尽管有时它会带来一些好处)。
请注意,C99 灵活数组成员根本不会浪费任何空间;结构的大小根本不包括灵活的数组成员。这比 struct hack 更容易使用;您不必担心以同样的方式浪费空间。
史蒂夫杰索普指出:
如果您要使此类结构可复制,那么无论如何您应该在结构中具有容量和大小字段。然后编写一个函数来复制它们,它检查容量、复制大小(但不是容量)并复制数据。如果你分别做这三件事,那么你不必担心布局。而且由于有很多事情要做,结构的用户不应该尝试自己复制它,他们应该使用函数。
当然,这正是我定义的原因dup_entry()
;确实有必要防止人们在复制时出丑。和函数必须实现为彼此一致,但这并不难make_entry()
。dup_entry()