【问题标题】:How do I properly allocate an array within a struct with malloc and realloc in C?如何在 C 中使用 malloc 和 realloc 在结构中正确分配数组?
【发布时间】:2017-07-27 13:43:49
【问题描述】:

我正在尝试在 C 中实现堆栈,同时也在尝试学习 C。我的背景主要是高级语言(如 Python),所以很多内存分配对我来说都是新的。

我有一个程序按预期运行,但发出警告,让我相信我做错了什么。

代码如下:

typedef struct {
    int num_items;
    int top;
    int items[];
} stack;

void push(stack *st, int n) {
    st->num_items++;

    int* tmp = realloc(st->items, (st->num_items) * sizeof(int));

    if (tmp) {
        *(st->items) = tmp;
    }

    st->items[st->num_items - 1] = n;
    st->top = n;
}

int main() {
    stack *x = malloc(sizeof(x));
    x->num_items = 0;
    x->top = 0;
    *(x->items) = malloc(0);

    push(x, 2);
    push(x, 3);

    printf("Stack top: %d, length: %d.\n", x->top, x->num_items);

    for (int i = 0; i < x->num_items; i++) {
        free(&(x->items[i]));
    }
    free(x->items);
    free(x);
}

这是输出:

Stack top: 3, length: 2.

这是预期的。但是在编译过程中,出现以下错误:

> gcc -x c -o driver driver.c
driver.c: In function 'push':
driver.c:16:16: warning: assignment makes integer from pointer without a cast
    *(st->items) = tmp;
...
driver.c: In function 'main':
driver.c:27:14: warning: assignment makes integer from pointer without a cast
    *(x->items) = malloc(0);

【问题讨论】:

    标签: c arrays pointers memory struct


    【解决方案1】:

    当你在结构的末尾声明了一个空数组时,它被称为flexible array member。而且你不是通过分配数组成员来分配它,而是通过分配整个结构

    例如

    stack *x = malloc(sizeof *x + sizeof s->items[0] * 32);
    

    上面的malloc 调用为结构本身分配空间(注意sizeof *x 的解引用运算符的使用)加上32 个元素的数组的空间。

    要么是上面的,要么把成员改成指针。

    【讨论】:

    • 更改成员将是合乎逻辑的方法,否则您需要使代码句柄 realloc 更改整个结构的内存位置
    • @ChrisTurner 是的,但它可以通过记住更新来轻松解决,例如x 正确。
    • @Someprogrammerdude @ChrisTurner 如果我在 main 和 push 中使用它做了类似 stack* tmp = realloc(st, sizeof(*st) + st-&gt;num_items * sizeof(st-&gt;items[0])); 的操作,然后将 st 分配为 tmp,那会是首选解决方案吗?还是只更换会员更好?
    • @Dylan 正如 Chris Turner 所指出的,使用指针成员可能更容易,但这里确实没有“正确”的解决方案。两者都是有效的,这取决于你自己的喜好,你想使用哪一个。
    【解决方案2】:

    这是一个未指定大小的数组

    int items[];
    

    这是一个指针

    int *items;
    

    后者是您与malloc/realloc 一起使用以利用动态分配的内存。

    另外,因为你正在做(例如)

    *(x->items) = malloc(0);
    

    ...您正在取消引用项目,使其变为 int,这就是您收到这些特定警告的原因。

    【讨论】:

      【解决方案3】:

      你的想法是正确的。通常——几乎总是这样——来自 C 编译器的警告是严重的编程错误的迹象,会导致严重的问题。引用Shooting yourself in the foot in various programming languages:

      C

      • 你在自己的脚下开枪。
      • 你朝自己的脚开了一枪,然后没有人能弄清楚你做了什么。

      问题是您在编码时好像 items 是一个指向 int 的 指针,但您已将其声明并定义为 灵活数组成员(FAM ),这是完全不同的野兽。而且由于分配给数组会产生错误,即

      x->items = malloc(0);
      

      将是一个错误,你想出了一些编译时只有警告的东西。请记住,错误总比警告好,因为它们会阻止你自己动手。


      解决方案是将items 声明为指向int 的指针:

      int *items;
      

      并使用

      x->items = ...;
      

      获得您期望的指针行为。

      还有,

      free(&(x->items[i]));
      

      非常错误,因为您从未分配过 ith 整数开头;它们是数组中的对象。另外,您不需要malloc(0);只需用空指针初始化:

      x->items = NULL;
      

      reallocfree 不会介意空指针。


      灵活的数组成员意味着结构中的最后一个元素是一个长度不定的数组,所以在malloc你也可以为它预留足够的内存:

      stack *x = malloc(sizeof x + sizeof *x->items * n_items);
      

      灵活的数组成员在 CPython 中用于诸如 strbytestuple 等长度不可变的对象 - 在其他地方使用 FAM 而不是指针会稍微快一些,并且可以节省内存 -尤其是较短的字符串或元组。


      最后,请注意您的堆栈越增长越慢 - 原因是您总是只多分配一个元素。相反,您应该将堆栈的大小缩放一个因子(1.3、1.5、2.0?),以便插入运行在O(1) 时间而不是O(n);并考虑如果realloc 失败会发生什么 - 也许你应该大声说出来!

      【讨论】:

      • 选择这个作为公认的解决方案,因为它在一个地方包含最多的信息,甚至可以帮助改进算法。谢谢!
      猜你喜欢
      • 2012-09-19
      • 2018-09-21
      • 2018-07-13
      • 2016-02-06
      • 2021-03-04
      • 1970-01-01
      • 2018-04-27
      • 1970-01-01
      • 2019-06-07
      相关资源
      最近更新 更多