如何为嵌套零长度数组分配内存?

时间:2018-08-10 07:28:45

标签: c arrays linux gcc data-structures

我想用嵌套的零长度数组创建一个结构环:

typedef struct Data_Block
{
      size_t Data_Len;
      char Buf[0];
}Block;

typedef struct Block_Ring
{
      int head;
      int tail;
      int full;
      int block_num;
      Block blk[0];
}Ring;

如何为包含32个块的环和一个包含16个Buf的环正确分配内存?因为如果我以合适的大小分配内存,那么Block的数量将变成一个。

1 个答案:

答案 0 :(得分:4)

基本问题

解决此任务之前必须解决一个基本问题:构造数组需要具有固定的已知大小的元素。

这是因为通过将元素大小乘以 i 到数组的基地址来定位数组元素 i 。仅当元素的大小存在(元素具有固定的大小)并且您知道它(已知大小)时,才可以执行该计算。

尽管您将Block定义为包含大小为零的成员(Buf是一个包含零个元素的数组),但您打算将其用作该成员为16个字节(16个数组{ {1}})。但是,无法告诉编译器您将分配和使用的char对象实际上是具有16个额外字节的Block对象。您当然可以为它们分配空间,我将向您展示如何使用它们,但是您打算如何使用它们?如果Blockx对象,并且您编写了Ring,则编译器将生成将x.blk[i]乘以i的大小的代码。 ,这是错误的,因为编译器认为Block的{​​{1}}字节为零,但是您的Block对象更大。

标准C与扩展名

将结构成员声明为具有零个元素的数组是一种扩展(在GCC中特别有用)。 1999 C标准引入了一个类似的功能,称为 flexible array member 。在标准C中,声明了一个灵活的数组成员,没有维,而不是零维。

柔性数组成员是不完整类型(C 2018 6.7.2.1 18)。换句话说,没有完全指定类型。数组的成员数未知,因此数组的总大小未知。

然后,在定义Buf时,我们不能将Block成员定义为一个灵活的数组成员,该成员是Ring的数组,因为标准C要求数组是完整类型(C 2018 6.7.6.2 1,“元素类型不得为不完整或函数类型”)。

因此,此代码不能成为标准C。这实际上是一个优点:C标准可防止您在创建因其元素大小未知而无法工作的数组方面犯上基本错误。 / p>

奇怪的是,用于x86-64的GCC 8.1无法对此进行诊断。它应该为约束违反提供诊断。 Apple LLVM版本9.1.0(clang-902.0.39.2)会发出诊断信息。

但是,我们将使用语言扩展名继续考虑您编写的代码。

这些元素有多大?

当C实现对结构进行布局时,必须确保结构中的每个成员正确对齐。 (正确的对齐方式是由实现定义的,因此它们会有所不同。但是,无论它们是什么,编译器都必须相应地对结构进行布局。)由于结构可以用作数组的元素,因此布局结构的大小必须保证当一个结构在数组中跟随另一个结构时,以下结构中的所有成员也都正确对齐。

要满足此约束要求,结构的大小必须是所有成员的对齐要求的倍数。例如,如果某些成员的对齐要求为4字节和8字节,则结构的大小必须为8字节的倍数,因为这是4字节和8字节的最小公倍数。实际上,所有对齐要求都是2的幂,因此所有对齐要求的最小公倍数就是最大(限制最大)的对齐要求。

这意味着,当为blk对象的数组分配空间时,您不能简单地为任意Block元素使用任意数量的字节。您必须确保每个Block对象的总大小是其成员对齐要求的倍数。

C提供了一种了解结构对齐要求的方法。表达式Buf是对齐要求。因此,如果希望每个Block_Alignof(Block)中具有Block个元素,则每个x所需的大小就是基础结构的大小(Buf ),再加上实际数组元素所需的大小(Block)以及足够的填充,以将总数取整为对齐要求的倍数。您可以使用以下方法进行计算:

sizeof(Block)

(这是一个众所周知的表达式,用于四舍五入为x * sizeof(char)的倍数。您可以修改一些示例以了解其工作原理。)

一旦使用上述代码计算出一个// Calculate desired space. size_t S = sizeof(Block) + x * sizeof(char); // Note the alignment requirement. static const size_t A = _Alignof(Block); // Round up to multiple of alignment requirement. S = (S-1) / A * A + 1; 所需的空间(A使用16个空间),就可以为一个Ring分配32个{{1} }使用:

Block

访问数组元素

现在您有空间,如何访问x的成员?如上所述,编译器不知道如何执行此操作。不幸的是,C不提供任何帮助。您将必须手动计算地址。由于您知道每个Block对象Ring *R = malloc(sizeof(Ring) + 32 * S); 的大小,因此可以使用以下公式计算索引为blk的块的地址:

Block

讨论

这很麻烦且容易出错。地址计算可以包装到一个辅助函数中,以使其变得更好一点。但是,使用这样的复杂代码通常不是一个好主意。您应该考虑其他解决方案。