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

标签 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 *数据,还是字符数据?特别是,我对 struct 如何工作的直观理解是,声明 data 的正确方法是 char data ,没有指针或数组表示法全部。最后,我在两种实现中对 basicsizeitemsize 的计算是否正确?特别是,这种使用 offsetof 是否保证对所有机器都是正确的?

更新

显然这被称为 struct hack :在C99中,您可以使用flexible array member :

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

您将在运行时malloc数据分配足够的空间。在 C99 之前,data[1] 声明很常见。所以我现在的问题是为什么声明 char data[1]char data[] 而不是 char *data字符数据

最佳答案

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

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

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

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

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

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

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

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

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

    char *as_array = &listarray.data;

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

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

关于c - 在动态分配的结构(数组结构)中分配动态数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28306424/

相关文章:

python - Numpy - 仅更改数组一列的值的更简单方法?

c# - C# 中的 C++ 结构

c 结构将自身作为参数传递给指针函数

c - 需要逐步说明“while(((c = getchar())!='\n'&& c!= EOF;”)

c - 将连续的制表符读取为空字段 fscanf

c - 仅运行预处理器但仅针对某些语句

c - 反转字符串

ios - Swift iOS - 如何按特定顺序对数组进行排序?

arrays - JSONB 或整数数组

c - 使用二叉搜索树进行井字棋(C)