区分堆栈和堆内存

时间:2017-01-23 20:55:30

标签: c free calloc

假设我有一个使用以下listnode_t的列表实现:

typedef struct ListNode {
    struct ListNode *next;
    struct ListNode *prev;
    void *value;
} listnode_t;

这是一个双向链表,如您所见。我还有一个list_t,它有两个指向listnode_t的指针作为列表的第一个和最后一个节点和大小。 现在我在主要的

中跟随
int main(int argc, char *argv[]){
    ...
    // Create two empty lists
    list_t *list1 = make_list();
    list_t *list2 = make_list();

    // Populate one with ints
    int x = 4; 
    int y = 5;
    list_push(list1, &x);
    list_push(list1, &y);

    // Populate other with strings
    string_t *str1 = make_string("foo");
    string_t *str2 = make_string("bar");
    list_push(list2, str1);
    list_push(list2, str2);
    ...

    // Delete at the end
    destroy_list(list1);
    destroy_list(list2);
}

我在实施destroy_list时遇到问题。这是我试过的;

void destroy_list(list_t *list)
{
    listnode_t *cur = list -> first;
    for(cur = list -> first; cur != NULL; cur = cur -> next){
        if(cur -> prev){
            free(cur -> prev);
        }
    }

    free(list -> last);
    free(list);
    list = NULL;
}

我的问题是,我尝试在void *中使用listnode_t来一般使用此列表。但是当我删除内容时,应用free(cur -> prev)似乎有问题。当我使用像上面这样的内容列表时,因为那些是在堆栈上分配的,所以事情进展顺利,我想。但是,如果我有上面的字符串列表,因为我在字符串实现中使用动态分配,我必须首先应用free(cur -> prev -> value)。我不知道怎么能这样做,因为如果我添加它,那么我会遇到另一个问题,试图释放主机上堆栈分配的内存。

我该怎么做,我没有得到这种通用列表行为。

5 个答案:

答案 0 :(得分:4)

// Populate one with ints
int x, y = 4, 5;
list_push(list1, &x);
list_push(list1, &y);

不要这样做。

您为list结构建立的接口有效地表示当调用list_push时指针的“所有权”传递给列表,并且指针将为free() d在destroy_list的列表中。将指针传递给堆栈变量会违反该接口 - 如果要传入整数,则需要为列表创建副本以获取所有权。

list结构的替代接口可能是将指针和长度传递给list_push,并使该函数获取结构的副本而不是保留它。这是否合适将取决于您对此结构的用例。

(另外,int x, y = 4, 5没有按照您的想法执行。您可能意味着int x = 4, y = 5。)

答案 1 :(得分:1)

  

int x,y = 4,5;

我认为这不符合你的想法。

  

但是当我删除内容时,应用cur -> prev似乎有问题。

“有问题”不是一个技术术语。准确描述您期望发生的事情,以及恰恰发生的事情。

  

当我使用像上面这样的内容列表时,因为那些是在堆栈上分配的,所以事情进展顺利,我想。

不,他们不是很好。当你在堆栈中获取变量的地址时,你会让自己失望,因为只要你的函数返回,堆栈就是垃圾。

  

但如果我有上面的字符串列表,因为我在字符串实现中使用动态分配,我必须首先应用cur -> prev -> value

“申请”是什么意思?你是什​​么意思“第一”?

  

我不知道怎么能这样做,因为如果我添加它,

“添加”是什么意思?它是什么”?添加到什么?在技​​术文本中,我们从不使用“it”和“that”这样的介词。拼写出来。

  

然后我又遇到了另一个试图释放主机上堆栈分配内存的问题。

同样,“问题”并没有多说。在任何情况下,如果我猜测您的问题是什么,解决方案很简单:始终为value分配空间,以便您可以随时释放它。

    if(cur -> prev){
        free(cur -> prev);
    }

这不是你从双向链表中删除节点的方式。为了从双向链表中删除节点Y,您必须首先取消链接节点Y,以便节点Z的prev指向节点X,并且节点X的next指向节点Z,然后你可以释放节点Y.(释放它的值之后。)当然你必须时刻记住,节点Y可能是列表中的第一个或最后一个节点,在这种情况下它没有上一个或下一个节点,而你必须修改列表结构本身的头部或尾部。

答案 2 :(得分:1)

用户必须提供销毁value的功能,因为只有列表的用户知道存储的内容和需要执行的操作。

为了使您的列表成为通用列表,make_list必须将作为参数的参数作为销毁值的函数。作为一个部分例子:

struct LISTROOT {
    listnode_t list;
    void (*freeitem)(void * value);
 };
 struct LISTROOT myList;

 struct MYTYPE {
     char *buffer;
     int *whatever;
 };
 void myFreeItem(struct MYTYPE *item)
 {
     if (item) {
         if (item->buffer) free(item->buffer);
         ...
         free(item);
     }
  }

 myList.list= make_list(myFreeItem);

并在您的代码中:

    if(cur -> prev){
        if (myList.freeitem) mylist.freeitem(cur->value);
        free(cur -> prev);

请注意,列表定义freeitem以获取void *值,而用户的myFreeItem则指向更复杂的struct。编译器接受此操作并允许您隐藏列表中存储的任何内容的复杂性。

答案 3 :(得分:0)

一个非常常见的模型是接口的调用者指定是否应该由容器释放内容。即

list_t *list1 = make_list(0); // dont free items
list_t *list1 = make_list(1); // free items

如果他们说'请免费',那么您只需致电free即可。通过调用者必须提供的通用“这里是一个免费回调”更简单(另一个答案建议)

答案 4 :(得分:0)

一个简单且相当安全的解决方案是让列表管理传入的值的副本。因此,列表负责正确分配和释放内存。 请参阅以下代码,以相应地调整struct ListNodelist_push - 函数。请注意,该结构不再包含指向该值的指针;它将值存储在变长成员value[]中,并为结构本身分配足够的内存。因此,稍后释放struct对象也会自动释放内存以获取值副本。

typedef struct ListNode {
    struct ListNode *next;
    struct ListNode *prev;
    char value[];
} listnode_t;

void list_push(list_t *t, void *value, size_t valueSize) {

    listnode_t *newNode = malloc(sizeof(listnode_t) + valueSize);
    memcpy (newNode->value, value, valueSize);

    // ... any code necessary for attaching the new node to the list goes here
}