C:在结构中定义一个动态数组。动态数组将保存不同结构的实例

C: Define a dynamic array inside a struct. The dynamic array will hold instances of a different struct

我正在为学校做一个自动推文生成项目。

我有 2 个结构

  1. WordProb
  2. WordStruct - 必须包含 WordProb 对象的动态数组。

结构的外观 [1]:https://i.stack.imgur.com/LGNQg.png

动态数组应初始化为 1 个 WordProb 对象的大小。对于每个新的 WordProb 对象,我必须按 1 WordProb 的大小重新分配()动态数组并将其插入。

我试过用

WordStruct * ws1 = (WordStruct *)malloc(sizeof(*ws1) + sizeof(WrodProbability));
ws1->dynamic_arr_of_WordProb_objects[0].occurances = 4;
ws1 = (WordStruct *)realloc(ws1, 2 * sizeof(WrodProbability));
ws1->dynamic_arr_of_WordProb_objects[1].occurances = 4;

我有3个问题:

  1. 如何正确分配 dynamic_arr_of_WordProb_objects[](见上图)?
  2. 我怎样才能重新分配它?
  3. 如何用新的 WordProb 对象“分配”它?

为什么我觉得我的方法不对:

  1. 我做到了

ws1->dynamic_arr_of_WordProb_objects[55].occurances = 44;

printf("Sizeof ws1: %d", sizeof(*ws1->next_words));

这打印了 28.... 如果它包含一个数组,而该数组包含每个至少 24 字节大的其他结构,那么它怎么可能是 sizeof(28)? 如果我从未创建过索引 55,我该如何访问它?

我是 C 的新手,所以每条指导都会有所帮助。 谢谢!


在 G.O.A.T @jsiller 回答之后,我想我可能已经明白了。 这是在结构中定义动态数组并稍后重新分配的正确形式吗?

typedef struct{
    int occurances;
}WrodProbability; 

typedef struct {
    WrodProbability next_words[];
}WordStruct;

int main()
{
    WordStruct * ws1 = malloc(sizeof(*ws1) + sizeof(WrodProbability));
    ws1->next_words[0].occurances = 1;
    ws1 = realloc(ws1, sizeof(*ws1) + 2 * sizeof(WrodProbability))
}

关于

的最后一个问题
ws1->next_words[55].occurances = 44;

问题。 当我释放(ws1->next_words)时,它会释放从 ws1->next_words[0] 到 [55] 的所有内存? 或者只有我用 realloc 指定的前 X 数量的索引?

谢谢


Free(sw1) 无法正常工作

    WordStruct * ws1 = malloc(sizeof(*ws1) + sizeof(WrodProbability));
    ws1->next_words[0].occurances = 1;
    ws1 = realloc(ws1, sizeof(*ws1) + 2 * sizeof(WrodProbability));
    ws1->next_words[1].occurances = 2;
    printf("%d %p\n", ws1->next_words[0].occurances, ws1->next_words); // prints - 1 00000250023914a8
    free(ws1);
    printf("%d %p", ws1->next_words[0].occurances, ws1->next_words); // prints - 1 00000250023914a8

如您所见,在我释放 (ws1) 后仍然存在内存泄漏。 如何正确释放动态数组?

您使用的是(或曾经是)struct hack

我会用一个不同的例子来解释这个:

struct foo {
   int size;
   int arr[];
}

结构的 arr 属性是一个大小为 0 的数组。结构的大小现在为 4,因为: (sizeof(size) = 4 + sizeof(arr) = 0) = 0.

要使用 arr,您现在可以分配 foo 结构的大小加上 err-type 乘以您想要拥有的元素数量。 让我们将这行代码转换为示例:

WordStruct * ws1 = (WordStruct *)malloc(sizeof(*ws1) + sizeof(WrodProbability));
struct foo *f = (struct foo *)malloc(sizeof(*f) + sizeof(int));

相当于:

struct foo *f = malloc(sizeof(*f) + 1 * sizeof(int));

在这种情况下,我们将分配一个大小为 1 的数组的结构 foo。 50 个元素,您可以将其调整为:

struct foo *f = malloc(sizeof(*f) + 50 * sizeof(int));

关于你对 realloc 的提问,realloc 会采用你想要调整到的完整大小,而不仅仅是你想要添加的大小。因此,如果我们想向具有 50 个元素的 foo 结构添加 25 个以上的元素,我们需要像这样重新分配它:

f = realloc(f, sizeof(*f) + 75 * sizeof(int));
//75 because 50 + 25 = 75

要分配数组中的第一个元素,您可以这样做:

f->arr[0] = 1;

您的代码是正确的,但会分配第二个元素:

ws1->dynamic_arr_of_WordProb_objects[1].occurances = 4;

为什么可以访问数组外的元素? C 不保护您访问未分配的内存,这是未定义的行为,可能导致您的程序崩溃。这就是为什么您应该始终存储数组的大小并在尝试访问数组之前检查它。

最后为什么这行打印的尺寸“错误”

printf("Sizeof ws1: %d", sizeof(*ws1->next_words));

这可能是由于你对 realloc 的错误使用造成的,但我不能肯定地说,因为你没有分享你的结构的代码,而且在你分享的图像中我找不到任何名为“的属性next_words".

大多数人所说的结构中的“动态数组”是指指向 dynamically-allocated 数组的指针,类似于:

struct WordStruct {
    size_t numWords;
    struct WordProbability *words;
};

另一方面,您似乎正在使用的以及您的其他答案讨论的是具有 灵活数组成员 的结构。后者比前者有一些优点,但也有一些缺点。正确使用这些是棘手的,在某种程度上我认为它们是专家功能。此外,灵活的数组成员被更好地描述为“可变”而不是“动态”,因为尽管它们的大小可能因结构的一个实例而异,但任何特定实例的大小在其生命周期内都不会改变。*

因此,当你说,

The dynamic array should be initialized with size of 1 WordProb object. For each new WordProb object i must realloc() the dynamic array by size of 1 WordProb and insert it.

,它表明您需要一个类似于上面的形式, 而不是 具有灵活数组成员的结构。从这个角度来看,

  1. How can i allocate the dynamic_arr_of_WordProb_objects[] (see picture above) correctlly?

作为结构成员的指针并没有什么特别之处。给定一个已经存在的 struct WordStruct(并且它本身不需要为此目的动态分配),您使用 malloc()。例如,

struct WordStruct ws = { 1, NULL };
ws.words = malloc(1 * sizeof(*ws.words));
// TODO: fail if ws.words == NULL
  1. How can I realloc it?

同样,作为结构成员的指针并没有什么特别之处。您以通常的方式使用 realloc()

ws.numWords += 1;
struct WordProbability *newWords = realloc(ws.words, ws.numWords * sizeof(*ws.words));
// TODO: fail if newWords == NULL
ws.words = newWords;
  1. How do I "assign" it with the new WordProb object?

假设分配/重新分配成功,数组中有一个可行的结构对象,但它的值是不确定的。您可以简单地分配给该对象的成员(因此不需要任何单独的结构)...

ws.words[ws.numWords - 1].occurrences = 42;

... 或者如果你想复制整个结构的值,那么你这样做,仍然使用赋值运算符 (=):

ws.words[ws.numWords - 1] = anotherWordProbStructure;

你接着说

I did

ws1->dynamic_arr_of_WordProb_objects[55].occurances = 44;

printf("Sizeof ws1: %d", sizeof(*ws1->next_words));

This printed 28.... How can this be sizeof(28) if it holds an array that holds other structures that are at least 24 byte big each?

因为你的sizeof表达的并不像你想的那样。 ws1->next_words 具有数组类型,因此当它作为操作数出现在大多数表达式中时,包括 *ws1->next_words,它会自动转换为指向第一个元素的指针。这有时被称为“衰减”。当您取消引用结果指针时,您会得到一个 struct WordProbability 类型的对象。 sizeof 运算符实际上并不计算表达式,但它 执行 return 该表达式类型的大小。也就是说,您的 sizeof(*ws1->next_words) 100% 等同于 sizeof(WordProbability)。无法使用 sizeof 来确定 FAM 的大小。你需要明确地跟踪它。

and how can i access index 55 if i never created it?

根据您提供的内容,尚不清楚数组是否足够长以在索引 55 处包含一个元素。如果是,那么问题就没有意义了。如果不是,则行为未定义。 “未定义”意味着它在锡罐上所说的:你不能依赖任何特定的行为,特别是,假设程序会崩溃甚至明显的行为不端是不安全的。当然,假设程序会按预期运行,甚至一个 运行 另一个程序以相同的方式运行也是不安全的。

When i free(ws1->next_words) it will free all the memory from ws1->next_words[0] to [55]? Or only the first X amount of indexes i specified with realloc?

首先请注意,如果 ws1->next_words 是一个灵活的数组成员,那么您首先不能(只是)释放它。您只能释放您之前分配的特定的、完整的对象,并且之后还没有释放。您不能单独为 FAM 分配内存,因此您也不能单独释放它。

但是,您当然可以释放传统的动态数组。在这种情况下,整个分配的对象被释放。这与访问了哪些元素无关,仅与分配块的大小有关,由 malloc / realloc 根据请求的大小选择。


最后,你问了一个(显然)FAM 用法的例子:

Free(sw1) not working correct

    WordStruct * ws1 = malloc(sizeof(*ws1) + sizeof(WrodProbability));
    ws1->next_words[0].occurances = 1;
    ws1 = realloc(ws1, sizeof(*ws1) + 2 * sizeof(WrodProbability));
    ws1->next_words[1].occurances = 2;
    printf("%d %p\n", ws1->next_words[0].occurances, ws1->next_words); // prints - 1 00000250023914a8
    free(ws1);
    printf("%d %p", ws1->next_words[0].occurances, ws1->next_words); // prints - 1 00000250023914a8

As you see there is still memory leak after i free(ws1). How can i free the Dynamic array correctly?

我没有看到内存泄漏的证据,只有在对象生命周期结束后尝试访问对象时出现的未定义行为。该示例中第二个 printf() 的输出未定义,因此没有意义。它不会告诉您有关 ws1 最初指向的对象是否已成功释放的任何信息。

free() 没有可测试的故障模式。如果程序传递的指针不满足 free() 的要求,则会导致未定义的行为。如果程序传递的指针确实满足 free() 的要求,那么它必须假定成功。


*有人可能会反对,说“你可以 realloc() 结构来改变 FAM 的大小”,但是重新分配最好被视为用一个不同的。虽然它将数据从原始对象复制到新对象,但新对象通常具有不同的地址,尤其是当它比旧对象大时,对象的地址是其身份的最基本方面。这具有实际影响。