关于如何在C结构中表示缓冲区

时间:2012-09-18 06:26:53

标签: c struct

我最近看到以下类似结构可以包含缓冲区的信息。我知道“buffer”字段可能指向缓冲区的起始地址,因此我们可以使用memcpy(pDst, pEntry->buffer, bufferLen)将缓冲区复制到另一个地方(pDst),但我们怎么能分配Entry

struct Entry
{
    // data fields.
    size_t bufferLen;
    unsigned char buffer[1];
};

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

的64位系统上

这种结构有很大的局限性(灵活的阵列成员样式和结构黑客样式),其中之一就是你不能简单地分配结构。您必须允许额外的空间并使用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()函数必须相互协商才能实现,但这并不难。