在动态分配的结构(数组结构)中分配动态数组

Allocating a dynamic array in a dynamically allocated struct (struct of arrays)

这个问题实际上是关于如何在 Python/C API (PyObject_NewVar, PyObject_VAR_HEAD, PyTypeObject.tp_basicsize and .tp_itemsize 中使用可变长度类型,但我可以问这个问题而不必理会 API。假设我需要在 struct.

中使用一个数组

我可以用两种方法之一创建列表数据结构。 (我现在只讨论 char 列表,但没关系。)第一个使用指针并需要 两次分配 。忽略 #includes 和错误处理:

struct listptr {
    size_t elems;
    char *data;
};
struct listptr *listptr_new(size_t elems) {
    size_t basicsize = sizeof(struct listptr), itemsize = sizeof(char);
    struct listptr *lp;
    lp = malloc(basicsize);
    lp->elems = elems;
    lp->data = malloc(elems * itemsize);
    return lp;
}

创建列表的第二种方法使用数组表示法和一次分配。 (我知道这第二个实现是有效的,因为我已经对其进行了非常彻底的测试。)

struct listarray {
    size_t elems;
    char data[1];
};
struct listarray *listarray_new(size_t elems) {
    size_t basicsize = offsetof(struct listarray, data), itemsize = sizeof(char);
    struct listarray *la;
    la = malloc(basicsize + elems * itemsize);
    la->elems = elems;
    return lp;
}

在这两种情况下,您都可以使用 lp->data[index] 访问数组。

我的问题是为什么第二种方法有效?为什么声明 char data[1] 而不是 char data[]char data[0]char *datachar data 中的任何一个?特别是,我对 struct 的工作原理的直觉理解是,声明 data 的正确方法是 char data,完全没有指针或数组符号。最后,我对 basicsizeitemsize 的计算在两个实现中是否正确 ?特别是,offsetof 的这种用法是否保证对所有机器都是正确的?

更新

显然这叫做 struct hack: In C99, you can use a flexible array member:

struct listarray2 {
    size_t elems;
    char data[];
}

理解您在运行时 malloc 足够 space data。在 C99 之前,data[1] 声明很常见。所以我现在的问题是为什么声明char data[1]char data[]而不是char *datachar data

您声明 char data[1]char data[] 而不是 char *datachar data 的原因是为了让您的结构 可直接序列化和反序列化。这在您将这些类型的结构写入磁盘或通过网络套接字等的情况下很重要。

以您的第一个需要两次分配的代码片段为例。您的 listptr 类型不能直接序列化。即 listptr.elems 和 listptr.data 指向的数据不在连续的内存中。没有办法read/write这种结构to/from磁盘具有泛型功能。您需要一个特定于您的 struct listptr 类型的自定义函数来执行此操作。即在序列化时,您必须先将 elems 写入磁盘,然后写入数据指针指向的数据。在反序列化时,您必须读取元素,将适当的 space 分配给 listptr.data,然后从磁盘读取数据。

使用灵活的数组成员解决了这个问题,因为 listptr.elem 和 listptr.data 驻留在连续的内存 space 中。因此,要序列化它,您只需写出结构的总分配大小,然后写出结构本身。在反序列化时,您首先读取分配的大小,分配所需的 space 然后将您的 listptr 结构读入 space.

您可能想知道为什么您真的需要这个,但它可能是一个非常宝贵的功能。考虑一个异构类型的数据流。如果您定义了一个 header 来定义您拥有的异构类型及其大小,并在流中的每个类型之前加上这个 header,您通常可以非常优雅和高效地序列化和反序列化数据流。

我知道选择 char data[1] 而不是 char data[] 的唯一原因是,如果您正在定义需要在 C99 和 C++ 之间移植的 API,因为 C++ 不支持对于灵活的数组成员。

此外,想指出的是,在 char data[1] 中,您可以执行以下操作以获得所需的总结构大小:

size_t totalsize = offsetof(struct listarray, data[elems]);

您还问为什么不使用 char data 而不是 char data[1]char data[]。虽然技术上可以使用普通的旧 char data,但在道德上(恕我直言)会被回避。这种方法的两个主要问题是:

  1. 你想要一个字符数组,但现在你不能直接访问 data 成员作为数组。您需要将指针指向 data 的地址才能将其作为数组访问。即

    char *as_array = &listarray.data;

  2. 您的结构定义(以及您的代码对该结构的使用)将完全误导阅读代码的任何人。当您真正指的是一个字符数组时,为什么要声明一个 char

鉴于这两件事,我不知道为什么有人会使用 char data 而支持 char data[1]。考虑到其他选择,它对任何人都没有好处。