分配连续内存以包含具有灵活数组成员的多个结构

时间:2019-02-11 00:57:01

标签: c memory flexible-array-member

考虑一个包含灵活数组成员的结构,如下所示:

typedef struct {
    size_t len;
    char data[];
} Foo;

我有一个未知数量的Foos,每一个的大小都是未知的,但是我可以确定我所有的Foos总共总共为1024个字节。 在知道每个Foo的长度之前,如何为一个Foos数组分配1024个字节,然后稍后填充该数组的成员?

虽然这样会引发段错误,但还是这样:

Foo *array = malloc(1024);
int array_size = 0;

Foo foo1;
strcpy(foo1.data, "bar");
array[0] = foo1;
array_size++;

Foo foo2;
strcpy(foo2.data, "bar");
array[1] = foo2;
array_size++;

for (int i = 0; i < array_size; i++)
    puts(array[i].data);

之所以这样做,是为了将所有Foos都保留在一个连续的内存区域中,以保持CPU缓存的友好性。

2 个答案:

答案 0 :(得分:1)

根本不能拥有一个foos数组,因为foo没有固定的大小,并且数组的定义特征是每个对象都有固定的大小并且与可从其索引进行基础计算。对于您想要的工作,索引array[n]必须知道foo[0]foo[1],...,foo[n-1]的完整大小,这是不可能的,因为该语言不知道这些大小;实际上,灵活数组成员只是从大小中排除,因此foo[1]将与foo[0]的数据“重叠”。

如果需要能够以数组的形式访问这些对象,则需要放弃在每个对象中放置一个灵活的数组成员。相反,您可以将所有数据放在最后,并在每个数据中存储一个指向数据的指针或偏移量。如果不需要以数组形式访问它们,则可以在分配的内存中构建一种链表,将到下一个条目的偏移量存储为每个条目的成员。 (例如,在大多数Unices中,请参见struct direntgetdents的工作方式。)

答案 1 :(得分:1)

正如其他人所指出的,您不能拥有Foo的C数组。但是,假设您愿意不定期地存储它们,只需要知道可能需要多少空间即可。这个答案说明了这一点。

N Foo个对象的数量。

S sizeof(Foo),它是Foo对象的大小,其中data的字节为零。

A _Alignof(Foo)

每个Foo对象必须从与 A 字节对齐的地址开始。设为 A 。填充的最坏情况是data数组是一个字节,要求在下一个Foo开始之前跳过 A -1个字节。

因此,除了Foo对象(包括其data)消耗的1024个字节外,我们可能还需要( N -1)•( A −1)字节用于此填充。 ( N -1是因为在最后一个Foo之后不需要填充字节。)

如果每个Foo至少有一个字节的data,则最大 N 可能是floor(1024 /( S +1 )),因为我们知道所有Foo对象及其数据最多使用1024个字节。

因此1024 + floor(1024 /( S +1)-1)*( A -1)字节就足够了-实际数据和floor(1024个字节) 1024 /( S +1)−1)*( A −1)用于填充。

请注意,以上假设每个Foo至少具有一个字节的data。如果一个或多个Foo的{​​{1}}字节为零,则 N 可能大于floor(1024 /( S +1))。但是,在任何这样的data之后,不需要填充,并且 N 对于每个这样的Foo不能增加一个以上(因为减少一个字节使用的空间不能使多于一个Foo)。因此,这样的Foo可以在需要 A −1个字节填充的其他地方给我们另外一个Foo,但是它本身不需要填充,因此填充的总量所需的不能增加。

因此,为Foo对象分配内存的计划是:

  • 分配1024 +楼板空间(1024 /( S +1)-1)*( A -1)字节。
  • 将第一个Foo放在分配的内存的开头。
  • 将每个连续的Foo放在上一个Foo(包括其Foo)结束之后的下一个 A 对齐的地址。

这当然不会产生一个数组,在分配的空间内只会产生大量data个对象。您将需要指针或其他寻址方法。

每C 2018 7.22.3.4 2:

  

Foo函数为malloc由大小指定且值不确定的对象分配空间。

因此,以不规则的方式切碎size返回的空间以用于多个对象并不适合该规范。我将把它留给其他人讨论,但是我还没有观察到C实现对它有问题。