我用C写了一个链表,我用一个void* 作为数据。问题是void* 可能指向用户通过malloc分配的内存,也可能指向静态分配的内存(只是声明了一个变量)。现在,当我删除一个Node时,我不能判断是否必须调用free()来释放用户分配的内存,或者是否不必调用free(),因为否则程序会崩溃。
这个问题有解决的办法吗?
下面是我的链表代码片段:
typedef struct Node{
struct Node* next;
void* data;
} Node;
typedef struct List{
struct Node* head;
struct Node* tail;
} List;
Node* lst_add(List* list, Node* insertPos, void* data){
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) return NULL;
newNode->data = data;
newNode->next = NULL;
if (insertPos != NULL){
Node* tmp = insertPos->next;
insertPos->next = newNode;
newNode->next = tmp;
return newNode;
}
if (list->head == NULL){
list->head = newNode;
list->tail = newNode;
return newNode;
}
list->tail->next = newNode;
list->tail = newNode;
return newNode;
}
Node* lst_delete(List* list, Node* n){
if (n == NULL || list->head == NULL) return NULL;
if (n == list->head){
Node* tmp = list->head;
list->head = list->head->next;
free(tmp->data); /* programm crahes here if tmp->data was not allocated with malloc */
free(tmp);
return tmp;
}
Node* current = list->head;
Node* prev = current;
while (current != NULL){
if (current == n){
prev->next = current->next;
free(current->data); /* programm crahes here if current->data was not allocated with malloc */
free(current);
return current;
}
prev = current;
current = current->next;
}
return NULL;
}
我希望能够做到这一点:
int i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, &i) // &i -> void*
还有这个
int* i = (int*)malloc(sizeof(int));
*i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, i) // i -> void*
提前感谢您的帮助!:)
如果我在这两种情况下调用free(),我的程序会崩溃,如果我根本不调用free(),在某些情况下会有内存泄漏。
2条答案
按热度按时间clj7thdc1#
这个问题有解决的办法吗?
你必须设计你的程序,让它知道是否应该把地址传递给
free
。一种方法是不将任何静态分配的内存放入数据结构中。如果您有静态数据要包含在结构中,您可以在数据结构的初始化期间将数据复制到动态分配的内存中并将其放入结构中。这样,数据结构中的所有项都使用动态分配的内存,因此它可以始终被释放。
满足设计要求的另一种方式是用指示是否动态分配的标志来标记结构中的每个项。
其他备选方案可以是:
2cmtqfgy2#
问得好事实上,没有办法(在C标准中,并避免可怕的黑客攻击)能够处理无效的free()调用。
定义数据结构的一种合适的通用方法是让客户端代码创建并传递一个处理销毁/释放的函数指针。
将调用该函数而不是
free()
,并将数据项作为参数传递。根据数据类型的不同,该函数可能会调用free()
,也可能什么都不做。这意味着代码的用户必须为他们想要存储的每个日期类型(或者更确切地说,以相同方式销毁的每个类型集)创建一个“析构函数”。
在传递/保存此函数指针的位置上有一些注意事项。即:
1.* 将函数指针保存在列表结构体中 *。
lst_delete()
将调用list->destruction_function(data)
而不是free。**优点:**这需要用户代码在创建列表时只传递一个函数,这非常干净。缺点:列表只能保存一种数据类型(或者说,以相同方式销毁的数据类型)。1. 让你的
lst_delete()
函数接受一个指向析构函数的指针作为参数 * -即Node* lst_delete(List* list, Node* n, void (*destruction_function)(void *));
并使用它。优点:每个物品可以是不同的类型,因为销毁功能可以不同。缺点:用户必须知道每个 * 特定 * 节点存储的数据类型,才能通过相应的析构函数。1. 将
lst_add(...)
中的析构函数作为指针传递,并将该指针保存在Node
结构体中的data
* 旁边。因此,lst_delete()
在参数n
中包含要调用哪个析构函数的信息。优点:这是最通用的方法,因为它可以处理同一结构中的不同类型的数据,并且在删除时不需要用户“记住”它们的类型信息。实施上述方法#2:
void *
数据项的说明:*即使你存储的是指针,并且free()可以被调用而不会出错,这仍然不是最好的方法!
1.对于客户端代码来说,将指针传递到无法通过简单的自由操作清除的数据项(例如,指向结构的动态指针,该结构具有指向已分配内存的内部指针,例如字符串)。对这样的数据调用free会造成内存泄漏。
1.用户可能希望即使在从数据结构中删除数据后也能使用数据。例如,像二叉搜索树这样的结构是作为数据的快速搜索结构构建的,所以只存储指向该数据的指针并保留它们是有意义的,即使它们从索引中删除。每次都强制删除的结构的限制性更强一些。
出于这些原因,销毁功能确实是要走的路。