[英]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 ListNode
和list_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.