【问题标题】:Allocating contiguous memory to contain multiple structs with flexible array members分配连续内存以包含具有灵活数组成员的多个结构
【发布时间】:2019-02-11 00:57:01
【问题描述】:

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

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

我有一个未知数量的 Foo,每个都是未知大小,但是我可以确定我所有的 Foo 加起来正好是 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);

想要这样做的原因是将所有 Foo 保存在一个连续的内存区域中,以便 CPU 缓存友好。

【问题讨论】:

  • 以灵活数组作为最后一个成员的结构不能用作其他结构的成员或数组元素。
  • 建议更改:char data[];char *data; Then the variable length data can be elsewhere rather than 'in line'. This it is easy to declare an array of those structs, similar to: Foo myArray[ 1024/sizeof( Foo ) ];` 注意:发布的结构代码不包含“标签”名称,大多数调试器使用“标签” ' 名称以便能够访问结构中的各个字段
  • @Govind Parmar 是否有另一种方法来创建具有可变数据大小的结构的内存连续数组?
  • 您的方法的一个明显问题是不能保证每个后续的Foo 都会适当地对齐。对齐通常需要填充,如果您已经计算了准确的存储量,则没有填充空间。
  • 没有取消引用?好的,然后为data 数组分配最大空间量。当数据数组很短时,这会浪费很多空间,但消除了取消引用。我唯一看到取消引用是一个问题是在尝试“序列化”结构以写入文件时

标签: c memory flexible-array-member


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    NFoo 对象的数量。

    Ssizeof(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) 个字节就足够了——1024 个字节用于实际数据和 floor( 1024/(S+1)−1)*(A−1) 用于填充。

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

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

    • 分配 1024 + floor(1024/(S+1)−1)*(A−1) 个字节。
    • 将第一个Foo 放在分配内存的开头。
    • 将每个连续的Foo 放在前一个Foo(包括其data)末尾之后的下一个A对齐地址。

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

    每 C 2018 7.22.3.4 2:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 2012-09-22
      • 2020-04-13
      • 2012-11-22
      • 2011-07-25
      • 1970-01-01
      相关资源
      最近更新 更多