C结构中的柔性数组成员

C结构中的柔性数组成员,c,c99,flexible-array-member,C,C99,Flexible Array Member,引用C-std第6.7.2.1节 struct s { int n; double d[]; }; 这是一个有效的结构声明。我正在寻找这种语法的一些实际用途。确切地说,这个构造如何比保持一个double*作为第二个元素更强大或更弱?或者这是“你可以用多种方式”的另一种情况 Arpan正是这个问题的答案。快速的答案是,这个结构将在结构内部包含double数组,而不是指向结构外部数组的指针。作为一个快速示例,您可以使用以下示例中的结构: struct s mystruct = malloc(siz

引用C-std第6.7.2.1节

struct s { int n; double d[]; };
这是一个有效的结构声明。我正在寻找这种语法的一些实际用途。确切地说,这个构造如何比保持一个double*作为第二个元素更强大或更弱?或者这是“你可以用多种方式”的另一种情况

Arpan

正是这个问题的答案。快速的答案是,这个结构将在结构内部包含
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
的数组必然会因这种情况而变得复杂


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

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

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

您可以使用它向动态分配的数组添加头字段,其中最常见的是其大小:

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;
struct int\u数组
{
大小;
int值[];
};
struct int_array*foo=malloc(sizeof*foo+42*sizeof*foo->value);
foo->size=42;
...
对于(大小i=0;isize;++i)
foo->values[i]=i*i;

通过使用
int*
成员并分别分配数组,可以获得类似的结果,但在内存(附加指针、第二个内存块的堆管理)和运行时(附加间接寻址、第二个分配)方面效率都会较低.

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


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

so struct s s1=malloc(…);然后结构s2=s1;是否意味着s2获得一个自动创建的数组,并复制s1的内容?如果不是POD类型结构,而是将用户定义的类作为第二个元素,是否同样适用?否,结构赋值不会发生神奇的复制;但是如果您使用大小合适的
memcpy()
,它就可以工作。如果你有一个指针,你需要分别分配内存和复制数组。我不确定C FAQ,q2.6的链接是否真的能回答这个问题。如果是这样的话,那只是在一种神秘的意义上,只有那些已经知道答案的人才能理解。事实上,链接表明,如果它谈论的是同一件事,就不能被视为是可移植的。@Arpan:您的示例不可能像编写的那样,因为如果
struct s
有一个灵活的数组成员,那么该类型是不完整的,您不能声明该类型的变量(您只能声明指向它的指针-
struct s*
). 不能将其更改为
struct s*s1=malloc();结构s*s2*s2=*s1方法的优点是,可以在数组长度的int和实际数组数据的开始之间获得良好的缓存局部性。啊,这是一个很好的例子,再次证明了数组和指针完全不同:)根据我的经验,实现了一个网络协议(或文件格式,本质上是相同的问题)通过类型双关将字节缓冲区放到结构类型上通常是错误的。相反,逐字段反序列化会更便于移植。@caf:逐字段反序列化更便于移植,但在某些情况下,类型双关可能会使代码更可读、更高效,尤其是当它可以构建数据表时指向存储在现有缓冲区中的内容的指针,而不必为所有信息的第二个副本分配空间,然后将所有信息从字节缓冲区复制到新分配的空间中。如果C支持“显式布局”,那么真正可移植的是什么结构,因此代码可以这样说,例如,“我需要一个64字节的数据类型,可以位于……任何2字节边界上,并且包括一个32位整数,称为“Woozle”,以小端顺序存储在偏移量12处,作为四个八位字节。”让编译器支持这类事情,并在与编译器的自然布局一致的情况下有效地处理它,比试图识别和优化
((uint32_t)ptr[15]上的所有不同变体要便宜得多