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

时间:2015-02-03 18:45:01

标签: c arrays struct malloc python-c-api

这个问题实际上是关于如何在Python / C API(PyObject_NewVarPyObject_VAR_HEADPyTypeObject.tp_basicsize and .tp_itemsize中使用可变长度类型,但我可以问这个问题而不必费心去做API的详细信息。假设我需要在struct内使用数组。

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

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:在C99中,您可以使用flexible array member

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

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

1 个答案:

答案 0 :(得分:1)

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

以第一个需要两次分配的代码段为例。您的listptr类型不能直接序列化。即listptr.elems和listptr.data指向的数据不在连续的内存中。无法使用通用函数从磁盘读取/写入此结构。您需要一个特定于struct listptr类型的自定义函数才能执行此操作。即在序列化时,您必须首先将elems写入磁盘,然后写入数据指针指向的数据。在反序列化时,您必须阅读elems,将适当的空间分配给listptr.data,然后从磁盘读取数据。

使用灵活的数组成员可以解决此问题,因为listptr.elem和listptr.data位于连续的内存空间中。因此,为了序列化它,您可以简单地写出结构的总分配大小,然后写出结构本身。在反序列化时,首先读取分配的大小,分配所需的空间,然后将listptr结构读入该空间。

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

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

另外,想指出在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

  3. 考虑到这两件事,我不知道为什么有人会使用char data来支持char data[1]。如果给出替代方案,那对任何人都没有好处。