【问题标题】:C linked list - when to free allocated memoryC链表-何时释放分配的内存
【发布时间】:2017-06-21 01:29:50
【问题描述】:

我有一个简单的链表实现,它带有 push、pop、unshift 和 shift 功能,可以根据需要添加/删除数据。我想确保我的实现在通过调用 pop 和 shift 检索数据时不会泄漏内存。

如何释放通过 malloc 分配的内存,同时将数据返回给调用者?

list.h

typedef struct _list_cell_t {
    void *data;
    struct _list_cell_t *next;
} list_cell_t;

typedef struct _list_cell_t *list_cell_ptr;

typedef struct {
    int size;
    list_cell_ptr head;
} list_t;

void list_init(list_t *p_list);
void list_free(list_t *p_list);

void list_push(list_t *p_list, void *data);
void list_unshift(list_t *p_list, void *data);

void *list_pop(list_t *p_list);
void *list_shift(list_t *p_list);

list.c

#include "list.h"

#include <stdlib.h>
#include <string.h>

void list_init(list_t *p_list)
{
    memset(p_list, 0, sizeof(list_t));

    p_list->head = NULL;
    p_list->size = 0;
}

void list_free(list_t *p_list)
{
    list_cell_ptr p_cell, p_next;

    p_cell = p_list->head;
    while (p_cell != NULL) {
        p_next = p_cell->next;
        memset(p_cell, 0, sizeof(list_cell_t));
        free(p_cell);
        p_cell = p_next;
    }

    memset(p_list, 0, sizeof(list_t));
}

void list_push(list_t *p_list, void *data)
{
    list_cell_ptr *p_curr_ptr, p_tmp;

    p_tmp = (list_cell_ptr)malloc(sizeof(list_cell_t));
    memset(p_tmp, 0, sizeof(list_cell_t));
    p_tmp->data = data;

    p_curr_ptr = &(p_list->head);
    while (*p_curr_ptr != NULL) {
        p_curr_ptr = &((*p_curr_ptr)->next);
    }

    p_tmp->next = NULL;
    *p_curr_ptr = p_tmp;
    p_list->size++;
}

void list_unshift(list_t *p_list, void *data)
{
    list_cell_ptr *p_curr_ptr, p_tmp;

    p_tmp = (list_cell_ptr)malloc(sizeof(list_cell_t));
    memset(p_tmp, 0, sizeof(list_cell_t));
    p_tmp->data = data;

    p_curr_ptr = &(p_list->head);

    p_tmp->next = *p_curr_ptr;
    *p_curr_ptr = p_tmp;
    p_list->size++;
}

void *list_pop(list_t *p_list)
{
    list_cell_ptr *p_curr_ptr = &(p_list->head);

    while ((*p_curr_ptr)->next != NULL) {
        p_curr_ptr = &((*p_curr_ptr)->next);
    }

    void *ret = (*p_curr_ptr)->data;

    *p_curr_ptr = NULL;
    p_list->size--;
    return ret;
}

void *list_shift(list_t *p_list)
{
    void *ret = p_list->head->data;

    list_cell_ptr p_next = p_list->head->next;

    p_list->head = p_next;
    p_list->size--;

    return ret;
}

【问题讨论】:

  • 没有 typedef、过多的括号、malloc() 的强制转换和 listhead 指针,这段代码可以简单易读。

标签: c memory-management linked-list malloc free


【解决方案1】:

如何释放通过 malloc 分配的内存,同时将数据返回给调用者?

总体而言,C 内存管理中的一般规则是,必须始终清楚释放每块动态分配的内存的责任在哪里,并且无论它在哪里,代码都必须小心地完成所有这些责任.在您的情况下,释放为给定列表分配的 struct _list_cell_t 对象的责任的唯一合理位置是再次从列表中删除这些对象的代码(popshiftfree函数)。

然而,在你释放每个这样的对象之后,你不能再次访问它,所以你必须首先将你打算返回的data指针存储在一个局部变量中。事实上,你已经这样做了。

有很多方法可以实现细节,但我建议使用这种范例:

  1. 将指向不再需要的struct _list_cell_t 的指针存储在局部变量中。
  2. 更新列表结构以删除该对象。
  3. 将指向所需数据的指针存储在局部变量中。
  4. 通过步骤(1)中记录的指针释放不需要的struct _list_cell_t
  5. 返回数据

【讨论】:

    【解决方案2】:

    您应该在覆盖值并释放您指向的地址之前使用您拥有的指针释放内存。您已经使用该指针来释放内存空间。

    void *list_pop(list_t *p_list)
    {
        list_cell_ptr *p_curr_ptr = &(p_list->head);
    
        while ((*p_curr_ptr)->next != NULL) {
            p_curr_ptr = &((*p_curr_ptr)->next);
        }
    
        void *ret = (*p_curr_ptr)->data;
        free(*p_curr_ptr); //free memory
        *p_curr_ptr = NULL;
    
        p_list->size--;
        return ret;
    }
    
    void *list_shift(list_t *p_list)
    {
        void *ret = p_list->head->data;
    
        list_cell_ptr p_next = p_list->head->next;
        free(p_list->head); //use this to free memory
        p_list->head = p_next;
        p_list->size--;
    
        return ret;
    } 
    

    希望我回答了你的问题

    【讨论】:

      【解决方案3】:

      您可以使用智能指针。

      这可以使用 C 中的结构来完成。我在 C 中包含了一个基本示例。它没有实现您拥有的所有功能(只是 push 和 pop),但它应该让您了解智能指针.

      #include <stdlib.h>
      
      // the client get's a pointer to this struct NOT the listitem
      struct data
      {
          int ref_count;
          int *data;
      };
      
      // this is only used by the main, push and pop functions
      struct listitem
      {
          struct listitem *next;
          struct data *the_data;
      };
      
      // the client will have to use decrement() when it is finished with the data
      int decrement(struct data *list_data)
      {
          if(list_data->ref_count > 0)
              list_data->ref_count--;
          if (list_data->ref_count == 0 && list_data->data)
          {
              free(list_data->data);
              list_data->data = 0;
              free(list_data);
              return 0;
          }
          return(list_data->ref_count);
      }
      
      // the client can use increment() if it passes the data to another variable
      struct data *increment(struct data *list_data)
      {
          if (list_data)
              list_data->ref_count++;
          return(list_data);
      }
      
      void free_list(struct listitem **pp)
      {
          while (*pp)
          {
              struct listitem *temp = (*pp)->next;
              decrement((*pp)->the_data);
              free(*pp);
              (*pp) = temp;
          }
      }
      
      void push_list(struct listitem **pp, int *data)
      {
          struct listitem *temp = (struct listitem *)malloc(sizeof(struct listitem));
          temp->next = (*pp);
          (*pp) = temp;
          temp->the_data = (struct data *)malloc(sizeof(struct data));
          temp->the_data->data = data;
          temp->the_data->ref_count = 1;
      }
      
      struct data *pop_list(struct listitem **pp)
      {
          if (*pp)
          {
              struct listitem *temp = (*pp)->next;
              struct data *d = (*pp)->the_data;
              free(*pp);
              (*pp) = temp;
              return(d); // the data is not being freed from memory here
          }
          return 0;
      }
      
      int main()
      {
          struct listitem *list = 0;
          int i;
      
          for (i = 0; i < 10; i++)
          {
              int *pi = (int*)malloc(sizeof(int));
              (*pi) = rand();
              push_list(&list, (int *)pi);
          }
          struct data *d[5];
          for (i = 0; i < 5; i++)
              d[i] = pop_list(&list);
          free_list(&list); // the linked list has been destroyed now
      
          // this would be the client code
          struct data *d2 = increment(d[0]);
      
          struct data *d3 = increment(d[1]);
      
          // clean up, delete all but d2 and d3
          for (i = 0; i < 5; i++)
              decrement(d[i]);
      
          // do something with d2(d[0]) and d3(d[1])
      
          // clean up time
          decrement(d2); // now this data has been deleted
          decrement(d3); // now this data has been deleted
      
          return 0;
      }
      

      确实没有其他方法可以在您的代码和客户端之间共享内存。

      【讨论】:

      • 他所需要的只是一个减少对结构的引用计数的函数,当 count == 0 时,如果适用,则释放(结构)和数据。
      • 如果该函数未被系统自动调用,则结果不是智能指针。并且按照这些方式设置和使用手动机制对于诸如 OP 之类的情况来说太过分了。
      • 是的,它不会被自动调用。它就像组件对象模型中的 obj->Release() 一样工作。
      • 那么您的建议是实现一个智能指针技术启发的辅助数据结构和函数库,并手动使用它?这与您的答案实际所说的相差甚远。这也是疯狂的矫枉过正。
      猜你喜欢
      • 1970-01-01
      • 2013-02-28
      • 1970-01-01
      • 1970-01-01
      • 2012-07-27
      • 1970-01-01
      • 1970-01-01
      • 2011-03-29
      • 2012-01-16
      相关资源
      最近更新 更多