C 结构中的灵活数组成员

Posted

技术标签:

【中文标题】C 结构中的灵活数组成员【英文标题】:Flexible array member in C-structure 【发布时间】:2011-03-04 02:46:51 【问题描述】:

引用 C-std 第 6.7.2.1 节,

struct s  int n; double d[]; ;

这是一个有效的结构声明。我正在寻找这种语法的一些实际用途。准确地说,这个构造比保持 double* 作为第二个元素更强大或更弱?或者这是“你可以通过多种方式做到”的另一种情况?

阿潘

【问题讨论】:

啊,这是一个很好的例子,再次证明了数组和指针根本不一样 :) 【参考方案1】:

C FAQ 正好回答了这个问题。快速的回答是,这个结构将在结构内包含double 数组,而不是指向结构外数组的指针。举个简单的例子,你可以像下面这个例子那样使用你的结构:

struct s mystruct = malloc(sizeof(struct s) + 5 * sizeof(double));
s.n = 12;
s.d[0] = 4.0;
s.d[1] = 5.0;
s.d[2] = 6.0;
s.d[3] = 7.0;
s.d[4] = 8.0;

等等——你关心的数组的大小包含在分配中,然后你可以像使用任何数组一样使用它。通常这种类型包含大小作为结构的一部分,因为使用+ 技巧跳过s 类型的数组必然会在这种情况下变得复杂。

对于您添加的问题“与将 [指针] 作为第二个元素相比,此构造的功能如何更强或更弱?”,它本身并没有更多 强大,但您不需要保留指针,因此您至少可以节省那么多空间-同样,当您复制结构时,您还将复制数组,而不是指向数组的指针-有时是细微的差别,但有时非常重要。 “您可以通过多种方式实现”可能是一个很好的解释,但在某些情况下您会特别想要一种设计。

【讨论】:

so struct s s1 = malloc (...);然后结构 s s2 = s1;是否意味着 s2 获得一个自动创建的数组并复制 s1 的内容?如果不是 POD 类型 struct s 将用户定义的类作为第二个元素,是否同样适用? 不,结构赋值不会发生神奇的复制;但是,如果您使用具有适当大小的memcpy(),它将起作用。如果你有一个指针,你需要分配内存并单独复制数组。 我不确定指向 C 常见问题解答 q2.6 的链接是否真的回答了这个问题。如果是这样,那只是在一种神秘的意义上,只有对已经知道答案的人才有意义。实际上,该链接表明,如果它谈论的是同一件事,则不应将其视为可移植的。 @Arpan:你的例子不可能像写的那样,因为如果struct s 有一个灵活的数组成员,那么类型是不完整的,你不能声明该类型的变量(你只能声明指向它的指针 - struct s *)。您也不能将其更改为struct s *s1 = malloc(); struct s *s2; *s2 = *s1;,因为这样做仍然会尝试访问不完整的类型。这些都不会编译。 struct s int n; double d[]; ; 方法的优点是您可以在具有数组长度的 int 和实际数组数据的开头之间获得良好的缓存局部性。【参考方案2】:

我已经看到这在 Windows 上用于按长度标记的字符串。字符数据在长度之后直接存储在内存中,将所有内容整齐地放在一起。

typedef struct 
    SIZE_T bytes;
    TCHAR chars[];
 tagged_string;

【讨论】:

【参考方案3】:

您可以使用它为动态分配的数组添加标题字段,其中最常见的一种是它的大小:

struct int_array

    size_t size;
    int values[];
;

struct int_array *foo = malloc(sizeof *foo + 42 * sizeof *foo->values);
foo->size = 42;

...

for(size_t i = 0; i < foo->size; ++i)
    foo->values[i] = i * i;

您可以通过使用int * 成员并单独分配数组来获得类似的结果,但是在内存(附加指针,第二个内存块的堆管理)和运行时(附加间接,第二次分配)。

【讨论】:

【参考方案4】:

主要优点是灵活的数组成员允许您为数组分配一个 单个 内存块以及结构中的其他数据(使用指针,您通常会以有两个单独分配的块)。

它对于通过相当多的网络协议传输的数据也很有用,其中传入流的定义方式相同——一个整数定义一个长度,然后是多个数据单位(通常是字节/八位字节)。您可以(通常)使用类型双关将具有灵活数组成员的结构覆盖到填充此类数据的缓冲区上,并直接使用它,而不必将其解析成片段然后单独使用这些片段。

【讨论】:

根据我的经验,通过将字节缓冲区键入到结构类型来实现网络协议(或文件格式,这本质上是相同的问题)通常是 You're Doing错了。相反,逐个字段反序列化它最终变得更加便携。 @caf:逐个字段反序列化更便于移植,但类型双关语在某些情况下可能使代码更具可读性和效率,特别是如果它可以构建一个指向存储在现有缓冲区,而不必为所有信息的第二个副本分配空间,然后将所有信息从字节缓冲区复制到新分配的空间中。如果 C 支持“显式布局”结构,那么代码可以说,例如,什么会使事情真正可移植? “我需要一个 64 字节的数据类型,可以定位... ...在任何 2 字节边界上,并且包括 [除其他事项外] 一个名为“Woozle”的 32 位整数,以小端顺序存储在偏移量 12 处作为四个八位字节。”编译器支持这类事情并在它与编译器的自然布局一致的情况下有效地处理它比尝试识别和优化(((uint32_t)ptr[15] &lt;&lt; 24) | ((uint32_t)ptr[14] &lt;&lt; 16) | ((uint32_t)ptr[13] &lt;&lt; 8) | ptr[12]) 上的所有不同变体更便宜,可以用几个 16 位加载替换地址 ptr+12 和 ptr+14,或从 ptr+12 加载单个 32 位。

以上是关于C 结构中的灵活数组成员的主要内容,如果未能解决你的问题,请参考以下文章

c语言中怎样通过索引获取结构体成员的值

c语言怎么结构数据初始化?

C中结构体内有一个成员是二维数组,可以直接赋值另一个一维数组吗?

C++ 如何将一个静态结构体数组初始化?

在C中参考命名的结构成员直接初始化或分配变量数组的结构成员?

C基础结构体