簡體   English   中英

區分堆棧和堆內存

[英]Differentiating between stack and heap memory

假設我有一個使用以下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;
}

我的問題是,我試圖在listnode_t使用void *以便能夠通用地使用此列表。 但是當我刪除內容時,應用free(cur -> prev)似乎有問題。 我猜想,當我使用像上面這樣的int之類的列表時,由於這些都是在堆棧上分配的,所以一切都很好。 但是,如果我有上述字符串列表,由於我在字符串實現中使用了動態分配,因此必須首先應用free(cur -> prev -> value) 我不知道該怎么辦,因為如果添加它,那么我將遇到另一個嘗試釋放main上分配的堆棧內存的問題。

我該怎么辦,我沒有這種通用的列表行為。

// 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

int x,y = 4,5;

我認為這並沒有您認為的那樣。

但是當我刪除內容時,應用cur -> prev似乎有問題。

“有問題的”不是技術術語。 准確描述您期望發生的事情,以及確切發生的事情。

我猜想,當我使用像上面這樣的int之類的列表時,由於這些都是在堆棧上分配的,所以一切都很好。

不,他們什么都沒有。 當您在堆棧上獲取變量的地址時,您會感到非常失望,因為一旦函數返回,堆棧便會變成垃圾。

但是,如果我有上述字符串列表,由於我在字符串實現中使用了動態分配,因此必須首先應用cur -> prev -> value

“申請”是什么意思? “第一”是什么意思?

我不知道該怎么做,因為如果我添加了它,

“添加”是什么意思? 它是什么”? 將其添加到什么? 在技​​術文獻中,我們從不使用“ it”和“ that”之類的介詞。 拼出來。

那么我遇到另一個問題,試圖釋放main上的堆棧分配的內存。

同樣,“問題”並沒有多說。 無論如何,如果我知道您的問題是什么,解決方案很簡單:始終為value分配空間,以便您始終可以釋放它。

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

這不是從雙向鏈表中刪除節點的方法。 為了從你必須先取消節點Y,這樣一個雙向鏈表中刪除節點Y prev的節點Z指向節點X,和next節點X指向節點Z,然后你就可以免費節點Y. (釋放其值之后。)當然,您必須始終牢記,節點Y可能是列表中的第一個或最后一個節點,在這種情況下,它沒有上一個或下一個節點,而您必須修改其中一個列表結構的頭部或尾部本身。

用戶必須提供破壞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的指針。 編譯器接受這一點,並允許您隱藏列表中存儲的內容的復雜性。

一個非常常見的模型是讓接口的調用者指定容器是否應該釋放事物。

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

如果他們說“請免費”,那么您就free給他們打電話。 調用者必須提供的通用“這里是一個免費的回調”更為簡單(由另一個答案建議)

一個簡單且相當安全的解決方案是讓列表管理傳入的值的副本。因此,列表負責正確分配和釋放內存。 請參見下面的代碼,它們相應地調整了struct ListNodelist_push 注意,該結構不再包含指向該值的指針。 而是將值存儲在可變長度成員value[]並為結構本身分配足夠的內存。 因此,稍后釋放結構對象也將自動釋放值副本的內存。

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
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM