【问题标题】:How can I allocate memory for nested zero length array?如何为嵌套的零长度数组分配内存?
【发布时间】:2018-08-10 07:28:44
【问题描述】:

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

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 个 Block 且一个 Block 包含大小为 16 的 Buf 的 Ring 正确分配内存?因为如果我 malloc 的大小合适,Block 的数量就会变成一个。

【问题讨论】:

  • malloc(sizeof(Ring) + sizeof(Block) * 32 + 32 * 16);
  • 用变量最后一个元素分配整个结构块是非常危险的,因为指针算法ptr++ 不再正常工作了。
  • 在结构中声明一个长度为零的数组是 GCC 和其他实现的扩展。从 C 2011 开始,您应该使用使用不完整数组类型的标准方法——只是省略长度。 char Buf[0]; 将改为 char Buf[];
  • 您尝试做的事情是不可能的,因为编译器不知道ring->blk[1] 和除此之外的元素的正确位置。因此,它将计算不正确的结构偏移量。如果你想让它工作,blk 需要是 pointers 到块的数组。
  • user694733 写的大部分是正确的。如果您将Buf 设置为灵活的数组成员,那么好的编译器将不允许您将blk 声明为Buf 的数组,因为它们的大小可能会有所不同,即使它们不一样,在编译时也是未知的。奇怪的是,如果您使用 0 扩展而不是标准的不完整数组类型,clang-902 允许它。但它必然会使数组算术错误。不过,您可以通过手动计算元素地址来使用它。

标签: c arrays linux gcc data-structures


【解决方案1】:

基本问题

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

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

虽然您定义Block 包含一个大小为零的成员(Buf 是一个具有零元素的数组),但您打算将该成员用作16 个字节(一个16 个char 的数组)。但是,无法告诉编译器您将分配和使用的 Block 对象实际上是具有 16 个额外字节的 Block 对象。你当然可以为它们分配空间,我会告诉你怎么做,但是你打算如何使用它们呢?如果xRing 对象,而您编写x.blk[i],编译器将生成代码,将i 乘以它认为Block 的大小,这将是错误的,因为编译器认为BlockBuf 的字节数为零,但您的 Block 对象更大。

标准 C 与扩展

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

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

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

因此,这段代码不能被制作成标准 C。这实际上是一个优势:C 标准防止你犯上面的根本错误,即创建一个无法工作的数组,因为它的元素大小未知。

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

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

元素有多大?

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

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

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

C 提供了一种了解结构对齐要求的方法。表达式_Alignof(Block) 是对齐要求。因此,如果您希望每个BlockBuf 中有x 元素,则每个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 的倍数。您可以修改一些示例来了解它的工作原理。)

一旦您使用上面的代码计算了一个Block 所需的空间(x 需要 16 个),您就可以使用包含 32 个 Block 的数组为一个 Ring 分配空间:

Ring *R = malloc(sizeof(Ring) + 32 * S);

访问数组元素

现在您有了空间,您如何访问blk 的成员?如上所述,编译器不知道如何执行此操作。不幸的是,C 不提供任何帮助。您将不得不手动计算地址。由于您知道每个 Block 对象的大小,S,您可以计算索引为 i 的块的地址:

Block *B = (Block *) ((char *) R->blk + S*i);

讨论

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-19
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多