Calling free() on unknown data.

huangapple go评论65阅读模式
英文:

Calling free() on unknown data

问题

I understand your question. Here's a summary of the issue:

你想解决的问题是:

You're working with a linked list in C, and you're using void* for the data. The challenge is that the void* can point to either dynamically allocated memory (using malloc) or statically allocated memory (just a variable declaration). When deleting a node, you need to determine whether to call free() to release the memory allocated by the user or not to avoid a program crash.

现在的问题是:

你的问题是:

Is there a solution to this problem?

这个问题是否有解决办法?

英文:

I‘m writing a linked list in C and I‘m using a void* for the data.
The problem is that the void* could point to memory that was allocated by a user with malloc or to statically allocated memory (just a variable that was declared).
Now when I delete a Node I can’t tell if I have to call free() to free the allocated memory by the user or if I don’t have to call free() because the program would crash otherwise.

Is there a solution to this problem?

There is a snippet of my linked list code:

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;
}

I want to be able to do this:

int i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, &i) // &i -> void*

... and this:

int* i = (int*)malloc(sizeof(int));
*i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, i) // i -> void*

Thanks for your help in advance! Calling free() on unknown data.

If I call free() in both cases my program crashes and if I don’t call free() at all I have memory leaks in some cases.

答案1

得分: 5

> 是否有解决这个问题的方法?

你必须设计你的程序,以便它知道是否应该将地址传递给 free

一种方法是不将任何静态分配的内存放入数据结构中。如果你有静态数据要包含在结构中,你可以在数据结构初始化期间将数据复制到动态分配的内存中,然后将其放入结构中。这样,数据结构中的所有项都使用动态分配的内存,因此它可以随时被释放。

满足设计要求的另一种方法是,用标志标记结构中的每个项,指示它是动态分配的还是静态分配的。

其他的替代方法可能包括:

  • 将可能要释放的数据的地址与已知静态数据的地址进行比较。
  • 设计你的算法,以使静态分配的数据永远不会从数据结构中移除。
英文:

> Is there a solution to this problem?

You must design your program so that it knows whether or not it should pass the address to free.

One way to do this is not to put any statically allocated memory into your data structure. If you have static data you want to include in the structure, you can, during initialization of the data structure, copy the data into dynamically allocated memory and put that into the structure. That way, all items in the data structure use dynamically allocated memory, so it may always be freed.

Another way to do satisfy the design requirement is to tag each item in the structure with a flag indicating whether it was dynamically allocated or not.

Additional alternatives could be:

  • Compare the address of the data potentially to be freed to the address or addresses of known static data.
  • Design your algorithms so that the statically allocated data is never removed from your data structure.

答案2

得分: 0

这是已翻译的部分:

"这是已翻译的部分:

这是一个不错的问题。事实上,在C标准内并避免丑陋的黑客操作的情况下,没有办法处理无效的free()调用。

定义数据结构的适当通用方式是由客户端代码创建并传递一个处理销毁/释放的函数指针。

该函数应该在将数据项作为参数传递的情况下调用,而不是调用free()。根据数据类型的不同,该函数可以调用free()或不执行任何操作。

这意味着代码的用户必须为他们想要存储的每种数据类型创建一个“析构函数”函数(或者更确切地说,每组以相同方式销毁的类型)。

有一些考虑要将该函数指针传递/保存在哪里。具体来说:

  1. 将函数指针保存在列表结构体内部lst_delete()将调用list->destruction_function(data)而不是free。 优点: 这要求用户代码仅在创建列表时传递该函数,非常干净。 缺点: 列表只能保存一个数据类型(或者更确切地说,以相同方式销毁的数据类型)。
  2. 使您的lst_delete()函数接受销毁函数的指针作为参数 - 即Node* lst_delete(List* list, Node* n, void (*destruction_function)(void *));并使用它。 优点: 每个项可以是不同类型,因为销毁函数可以不同。 缺点: 用户必须知道每个具体节点存储的数据类型,以传递适当的销毁函数。
  3. lst_add(...)中将销毁函数作为指针传递并将其保存在Node结构的data旁边。因此,lst_delete()具有在参数n中的信息,指示要调用哪个销毁函数。 优点: 这是最通用的方法,因为它可以处理相同结构中不同类型的数据,而且用户在删除发生时无需“记住”其类型信息。 缺点: 实施起来需要更多的工作。"

"实施方法#2的注意事项:"

Node* lst_delete(List* list, Node* n, void (*destruction_function)(void *)){
    if (n == NULL || list->head == NULL) return NULL;
    
    if (n == list->head){
        Node* tmp = list->head;
        list->head = list->head->next;
        destruction_function(tmp->data); /*如果tmp->data没有使用malloc分配,则程序会在此处崩溃 */
        free(tmp);
        return tmp;
    }   
    
    Node* current = list->head;
    Node* prev = current;
    
    while (current != NULL){
        if (current == n){
            prev->next = current->next;
            destruction_function(current->data);   /* 更改在此处 */
            free(current);
            return current;
        }
        prev = current;
        current = current->next;
    }
    return NULL;
}

// -----“客户端代码从现在开始”-----

void do_nothing(void *x){
   ;
}

void free_a_pointer(void *x){
  free(x);
}


int i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, &i);
lst_delete(pointer_to_list, pointer_to_delete_position, do_nothing);



int* i = (int*)malloc(sizeof(int));
*i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, i);
lst_delete(pointer_to_list, pointer_to_delete_position, free_a_pointer);

“关于在结构上删除void *数据项的说明:”
即使您存储指针并且可以调用free()而不出错,这仍然不是最佳的方法!。因为:

  1. 客户端代码可以传递指向无法通过简单的free操作清除的数据项的指针是完全正常的(例如,指向具有内部指向分配内存的结构的动态指针,如字符串)。在这样的数据上调用free会导致内存泄漏。

  2. 用户可能希望在从数据结构中删除后仍然可以使用数据。例如,某些结构,如二叉搜索树,是用作数据的快速搜索结构,因此只存储指向数据的指针,即使从索引中删除它们,也可以保留它们。强制每次都删除的结构更加受限制。

因此,销毁函数确实是正确的方法。"

英文:

That's a nice question. Indeed, there is no way (within the C standard and by avoiding hideous hacks) to be able to handle invalid free() calls.

A proper generic way of defining your data structure would be to have the client code create and pass a function pointer that handles the destruction/freeing.

That function is to be called instead of free() with the data item passed as argument. Depending on what the data type is, that function may call free() or do nothing.

That means that the user of the code will have to create one "destructor" function for each date type they want to store (or rather, each set of types that are destroyed the same way).

There are some considerations on where to pass/save this function pointer. Namely:

  1. Save the function pointer inside your list struct. lst_delete() will be calling list->destruction_function(data) instead of free. Pros: This requires the user code to pass that function only ones at the creation of the list which is very clean. Cons: The list can save only a single data type (or rather, data types that are destroyed the same way).
  2. Make your lst_delete() function accept a pointer to the destruction function as an argument - i.e. Node* lst_delete(List* list, Node* n, void (*destruction_function)(void *)); and use that. Pros: Each item can be of different type, since the destruction function can be different. Cons: The user must know what type of data each specific node stores in order to pass the appropriate destruction function.
  3. Pass the destruction function in lst_add(...) as a pointer and save that pointer within the Node struct alongside the data. Therefore, lst_delete() has the information within argument n of which destruction function to call. Pros: This is the most generic way as it can handle different types of data within the same struct and there is no need of the user "remembering" their type information when deletion happens. Cons: More effort to implement.

Implementation of approach #2 from above:

Node* lst_delete(List* list, Node* n, void (*destruction_function)(void *)){
    if (n == NULL || list->head == NULL) return NULL;
    
    if (n == list->head){
        Node* tmp = list->head;
        list->head = list->head->next;
        destruction_function(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;
            destruction_function(current->data);   /* change here */
            free(current);
            return current;
        }
        prev = current;
        current = current->next;
    }
    return NULL;
}

// ----- "client code from now on" -----

void do_nothing(void *x){
   ;
}

void free_a_pointer(void *x){
  free(x);
}


int i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, &i);
lst_delete(pointer_to_list, pointer_to_delete_position, do_nothing);



int* i = (int*)malloc(sizeof(int));
*i = 5;
lst_add(pointer_to_list, pointer_to_insert_position, i);
lst_delete(pointer_to_list, pointer_to_delete_position, free_a_pointer);

A Note on deleting void * data items on structures:
Even if you are storing pointer and free() can be called without error, this is still not the best way to do it!

  1. It is perfectly normal for the client code to pass pointer to data items that cannot be cleared by a simple free operation (e.g. a dynamic pointer to a struct who has internal pointers to allocated memory, such as strings). Calling free on such data creates memory leaks.
  2. The user might like to have the data available even after deletion from your data structure. E.g. some structs like binary search trees are built as fast search structures for your data, so it makes sense to store only pointers to that data and keep them even if they are removed from the index. A struct that imposes deletion every time is a bit more restrictive.

For those reasons, the destruction function is indeed the way to go.

huangapple
  • 本文由 发表于 2023年5月17日 19:51:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76271790.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定