【问题标题】:C - Who is responsible for memory allocation when memory is allocated inside a function?C - 在函数内部分配内存时,谁负责内存分配?
【发布时间】:2021-01-07 16:34:45
【问题描述】:

我正在努力提升我的 C 级游戏。我目前正在使用 Kyle Loudon 的《用 C 语言掌握算法》一书。目前我正在研究数据结构,特别是堆栈。我将在这里使用的代码示例并非直接来自本书,而是为了便于阅读而稍作修改。我上网查了一下,但没有找到任何问题的答案。

我在 C 编程中经常听到的一条常见规则是“分配内存的人负责释放它”。这似乎是一个简单的规则。但是,当我尝试创建一个函数以将新的堆栈元素压入堆栈顶部时,我有点不确定。

代码

StackElmt 是驻留在 Stack 中的元素。在对堆栈执行任何操作之前,需要运行 stack_init。之后,stack_push 将新元素压入栈顶,stack_pop 将它们从栈中弹出。

typedef struct StackElmt_ {
    void *data;
    struct StackElmt_ *next;
}StackElmt;

typedef struct Stack_
{
    int size;
    StackElmt *top;
}Stack;

void stack_init(Stack *stack)
{
    stack->size = 0;
    stack->top = NULL;
}

void stack_push(Stack *stack, void *data)
{

    StackElmt *element = calloc(1, sizeof(StackElmt));
    element->data = data;
    element->next = stack->top;
    stack->top = element;
    stack->size++;
}

void stack_pop(Stack *stack, StackElmt **element)
{
    *element = stack->top;
    stack->top = (*element)->next;
    stack->size--;
}

问题描述

我觉得很奇怪 stack_push 将内存分配给堆,但 stack_pop 没有释放它。我觉得如果 stack_push 分配了内存,内存管理就变成了这些 Stack 函数的开发者而不是用户的责任。

相反,我想做这样的事情:

void stack_push(Stack *stack, StackElmt *element, void *data)
{
    element->data = data;
    element->next = stack->top;
    stack->top = element;
    stack->size++;
}

一个指向 StackElmt 的指针被输入,用户可以自己选择如何分配它。

总结

我的问题是:

  1. 在 stack_push() 中分配内存但在 stack_pop() 中未释放的代码是否被认为是好的设计?在 stack_pop() 中明确注释用户负责 StackElmt 内存会使设计更好吗?
  2. 在书中,还有一个叫做“destroy_stack”的函数可以销毁堆栈。在其中,“stack_pop”和一个指针指向的用户定义函数(建议为免费的)针对堆栈中的每个元素运行。从用户的角度来看,这个“销毁”功能是否让负责内存管理的人更清楚?
  3. 由用户完全分配内存(参见我修改过的 stack_push)还是由程序完全分配更好?

提前谢谢大家!

【问题讨论】:

  • 如果 pop 操作在调用时释放了内存,那么如何获取刚刚从堆栈中弹出的数据?
  • 如果你问我,这是一个奇怪的设计。 pushpop 在这里是不对称的,这不是常规做法。 push 正在推送一些 data,而 pop 由于某种原因正在返回整个堆栈元素结构(这就是它无法释放它的原因)。我希望它能够返回数据。
  • 这是一个糟糕的设计。但是你的解决方案也不好。 StackElmt 类型应该对用户完全隐藏。所以push 应该接受数据并且pop 应该返回数据(或者你可以有一个top 函数来返回数据,在这种情况下pop 不返回任何东西,只处理内部簿记)

标签: c memory-management


【解决方案1】:

stack_pop() 不应该返回 StackElmt,它应该返回元素中的 data。这反映了stack_push() 的操作。

然后它可以释放元素。

void stack_pop(Stack *stack, void **data)
{
    if (!stack->top) {
        // report stack underflow error somehow
        return;
    }
    *data = stack->top->data;
    StackElmt *temp = stack->top;
    stack->top = temp->next;
    stack->size--;
    free(temp);
}

【讨论】:

    【解决方案2】:

    stack_pop 函数不应该暴露节点类型,而是返回数据。通过这种方法,可以在函数中释放节点。我们还应该确保在弹出元素之前堆栈不为空。这是修改后的版本:

    void stack_pop(Stack *stack, void **data)
    {
        StackElmt *oldTop;
    
        assert(stack != NULL);
        assert(stack->size > 0);
    
        *data = stack->top->data;
        oldTop = stack->top;
        stack->top = stack->top->next;
        free(oldTop);
        stack->size--;
    }
    

    您可能还想定义一个 stack_size 函数,以便客户端可以在调用 stack_pop 之前检查堆栈是否为空。

    【讨论】:

      猜你喜欢
      • 2015-05-07
      • 2012-05-05
      • 2013-07-01
      • 2023-03-28
      • 2012-06-02
      • 2016-07-29
      • 1970-01-01
      • 2017-12-13
      • 1970-01-01
      相关资源
      最近更新 更多