我最近看到以下类似结构可以包含缓冲区的信息。我知道“buffer
”字段可能指向缓冲区的起始地址,因此我们可以使用memcpy(pDst, pEntry->buffer, bufferLen)
将缓冲区复制到另一个地方(pDst
),但我们怎么能分配Entry
?
struct Entry
{
// data fields.
size_t bufferLen;
unsigned char buffer[1];
};
答案 0 :(得分:4)
这是旧的,前C99,'struct hack'的一个例子,它在C99中被转换为'灵活的数组成员'。假设您要存储字符串。您可以使用以下命令动态分配空间:
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
。
这种结构有很大的局限性(灵活的阵列成员样式和结构黑客样式),其中之一就是你不能简单地分配结构。您必须允许额外的空间并使用memmove()
或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
已经存储了一个字符。
有趣的评论 - 而且是轻描淡写。有一次有另一个答案(现已删除),它收集了一些有趣和适当的评论,我即将加入这个答案。
Michael Burr注意到:
memcpy(pDst, pEntry, offsetof(struct Entry, buffer[pEntry->bufferLen]))
将是一个更安全的习惯用法 - 如果在结构的末尾数组hack之前碰巧有多个条目,则不必手动考虑所有这些条目,并且它允许编译器自动处理hack数组之前的任何填充。同样,实例的分配可以是:pEntry = malloc(offsetof(struct Entry, buffer[desiredVarArrayElements]))
这是一个有趣的观察,与你的建议非常一致。这种offsetof()
的使用导致结果不是编译时常量;结果取决于在运行时用作下标的值。这是非正统的,但我认为没有错。用作下标的大小在计数中包含空字节至关重要。
这也引出了一个有趣的观察结果,如果这种技术用于短字符串,你最终可能会为结构分配比sizeof(struct Entry)
更少的字节。这是因为整个结构在大多数32位机器上都是4字节对齐,而在sizeof(size_t) == 8
(通常是64位Unix系统,但不是64位Windows)的大多数机器上都是8字节对齐的。
因此,如果要分配的字符串只有3个字节(2个字符和1个空终止符字节),则指定给malloc()
的大小为7或11(分别在32位或64位机器上) ),与结构尺寸为8或16相比。
因此,我为'struct hack'结构编写的代码(如问题中所示)采用(安全)快捷方式并分配所需的总内存,可能是4个字节或8个字节。有可能减少这种过度分配。请注意,内存分配器本身通常会分配一个向上舍入的大小 - 通常(但不一定)在32位系统上为8字节的倍数,在64位系统上为16字节。这意味着尝试分配更少的字节可能没有你期望的那么多好处(虽然它在某些时候会有一些好处)。
请注意,C99柔性阵列成员根本不会产生任何浪费空间;结构的大小根本不包括柔性阵列构件。这使得它比struct hack更容易使用;你不必以同样的方式担心浪费的空间。
Steve Jessop注意到:
如果您正在使这样的结构可复制,那么无论如何您应该在结构中具有容量和大小字段。然后编写一个复制它们的函数来检查容量,复制大小(但不是容量),然后复制数据。如果你分别做这三件事,那么你不必担心布局。由于有很多事情要做,结构的用户不应该尝试自己复制,他们应该使用该功能。
这当然是我定义dup_entry()
的原因;确实有必要防止人们在复制时愚弄自己。 make_entry()
和dup_entry()
函数必须相互协商才能实现,但这并不难。