C语言 释放具有void* 值的数据结构

c7rzv4ha  于 2023-06-05  发布在  其他
关注(0)|答案(3)|浏览(181)

我是一个C编程的初学者,我相信这个问题已经解决了,但是我找不到太多的答案。在我的程序中,我有一个BST(二进制搜索树)节点的实现:

typedef struct bstnode{
    char *key;
    void *value;
    struct bstnode *left;
    struct bstnode *right;
}bstnode;

其中我需要value为void*,以便将同一结构用于不同的目的。然而,我担心的是,在这样做的过程中,我允许在我的程序中有更多的错误和不稳定的可能性。例如:在许多情况下,关键字指向存在于堆栈中的值。这很好但是,有时我需要对密钥执行动态分配。有时,价值也是动态分配的。这意味着对于我创建的每一棵树,我必须执行不同的释放例程(并且,如果值是另一个动态分配的结构,我还需要确保正确地释放该结构)。当然,我可以跟踪哪个结构是哪个结构,但我担心这可能会失控并导致内存泄漏,以及更少的“可维护”代码库(例如,“可维护”代码库)。必须为特定的使用情况做出特定的解除分配功能)。我的问题是:我做的是不是不好的练习?有没有比为结构的每个用例编写函数更好的方法呢?

cbjzeqam

cbjzeqam1#

管理对象的生命周期是编程中的一个非常大的主题,转移管理对象生命周期的权限也是如此。有多种方法可以解决这个问题。

  • 你可以限制只使用标准C API的动态分配。
  • 您可以限制所有值都由用户创建和销毁。你的API只接受和返回要添加和删除的值,用户管理内存。这总体上将API设计限制为大多数简单函数。参见bsd queue.h https://github.com/openbsd/src/blob/master/sys/sys/queue.h#L175。
  • 您可以在需要时传递一个可选的释放函数和任何其他函数。这会导致每次你想做一些事情时,函数参数都非常非常长,但是对于“特别”函数来说是很好的。
void bsttree_free_cb(bsttree *tree, 
          void (*deallocate)(void *value, void *cookie),
          void *cookie) {
    for (every node to free) {
         if (deallocate) {
             deallocate(value, cookie);
         }
     }
}
void bsttree_free(bsttree *tree) {
    bsttree_free_cb(tree, NULL, NULL);
}
  • 将释放函数(-s)与树一起存储,并在释放树时调用它。这是一个伟大的整体解决方案。例如,使用cookie,您可以传递堆栈上分配的所有键的索引,并将它们从free()中排除。总的来说,这是你可以考虑的方式。
typedef struct bsttree {
    struct bstnode *head;
    void (*deallocate_value)(void *value, void *cookie);
    void (*deallocate_key)(void *key, void *cookie);
    void (*other_operation_value)(void *value, void *cookie);
    void *cookie;
} bsttree;
  • 你可以存储一个包含每个值和键的释放函数,并在释放时调用它。这将(大大)增加每个元素的大小。
typedef struct bstnode {
    char *key;
    void *value;
    void (*deallocate_value)(void *value, void *cookie);
    void (*deallocate_key)(void *key, void *cookie);
    void (*other_operation_value)(void *value, void *cookie);
    void *cookie;
    struct bstnode *left;
    struct bstnode *right;
} bstnode;
  • 最后,您将在每个类型上编写模板化的代码。使用宏或代码生成(X宏或通过替换宏)为用户需要的每个释放函数和类型生成函数。您可能对https://github.com/stclib/STC/tree/master项目感兴趣。

您可能对https://docs.gtk.org/glib/struct.Tree.html感兴趣。

zc0qhyus

zc0qhyus2#

我做的是不是不好的练习?有没有比为结构的每个用例编写函数更好的方法呢?
这些都是好问题。你注意到了一些重要的细节。
底线是,如果你想适应最一般的情况,你的节点可以包含指向不同类型和出处的值的指针,那么是的,你需要以某种方式提供信息,允许你的树操作例程在需要时触发正确的清理。
一些可能性包括:

  • 在每个节点中,包含一个指向用于为该节点释放数据的函数的指针。这是可用的最一般的替代方案之一。
  • 为树操作函数配备参数,通过这些参数可以提供一个(指向一个)释放回调函数,它们将使用该回调函数释放任何要删除的节点的数据。这通常是足够的,但它不容易处理同一树中的不同数据需要不同清理机制的情况。
  • 提供一个表示整个树的结构,并将适当的清理函数附加到该结构上。这允许比前一个选项更清晰的API,但归结起来几乎是一样的。
9jyewag0

9jyewag03#

在数据结构中处理这种情况的最常见方法是认识到数据结构将指向它们没有分配和控制的数据。
所以处理这个问题的常用方法是在C程序中创建可以添加到数据结构中的对象。但是执行分配的代码层也应该执行释放。它还应该避免在BST中留下一个指向已释放对象的节点。
因此,为您的API提供一个bst_delete(bsttree*, char* key)函数,它将从bst中删除bstnode(并释放它)。但不会释放指向的实际对象。因为那不是它的工作。
调用代码应该先调用bst_delete(),然后调用free(),这是需要删除的对象。
不要将在堆栈上分配的对象添加到bsttree,除非在使用该堆栈上下文的函数的生存期内,树也被销毁。销毁bsttree时,您需要删除它的所有节点以避免内存泄漏。

相关问题