我可以有一个 C 例程,它可以防止 free()ing 内存两次吗?

Can I have a C routine, which prevents free()ing the memory twice?

经常在项目中使用复杂的结构,例如,如下所示:

struct opts {
    char* server;
    char* port;
    int protocol;
    void* protocol_data;
};

为了释放这样的结构,直到今天我采用了如下套路:

void free_proto1(struct opts* opt);
void free_proto2(struct opts* opt);

void free_opts(struct opts** opt) {

    free((*opt)->server);
    free((*opt)->port);

    if ((*opt)->protocol == PROTOCOL1) {
        free_proto1(*opt);
    }
    else if ((*opt)->protocol == PROTOCOL2) {
        free_proto2(*opt);
    }
    else
        free((*opt)->protocol_data);

    free(*opt);
    *opt = NULL;
}

但是如果我有另一个像struct opts* opts2 = opts1这样的struct opts指针,在调用free_opts(&opt1)之后再调用free_opts(&opt2)肯定会导致程序崩溃。我知道编码的一个好习惯是避免这样的调用。但是有没有机会,我可以检测到内存已经被释放了?我什至有兴趣查看 Process Control Block(我认为这是所有程序信息所在的位置)。我可以在执行 free()'ing 操作之前仔细检查 PCB 结构,这样我就可以避免 free()'ing 内存两次吗?

释放时将指针设置为已知的无效值 - 通常为 NULL。

那么即使你多次释放 - free 忽略 NULL 指针。

您可以使用计数为 API 的引用,即:向您的结构添加一个 size_t refs 字段,然后添加一个

struct opts* ref_opts(struct opts* opt)

API 这将增加 ref 计数器和 return opt;最后,将 free_opts 重命名为 unref_opts() 并且只有当 refs 字段为 0 时才真正释放你的结构。

这将公开一个已知的 API 以获取结构引用并以非常相似的方式释放它;不使用就是用户的错

不幸的是,C 不支持像 f.e 这样的 smart pointers。 C++ 有。

在C中,你总是要小心不要引起内存泄漏。


总之,你可以f.e。以reference counting的方式提供另一个参数。这样做的缺点是每次调用 free_opts 时都需要将引用指针的数量作为参数传递给分配的内存,并且数量必须固定,但这是一种帮助您解决问题的方法。

仅当所有 个引用已被"pseudo-freed" 时才释放内存。

所有传递的引用指针,除了最后一个,都只是一个空指针,指向的内存实际上不会被释放,直到最后一个引用指针已经过去。

int free_opts (struct opts** opt, int ref) {

    static int cnt = 0; 
    cnt++;

    if ( cnt != ref ) {        
        *opt = NULL;
        return 0;
    }

    free((*opt)->server);
    free((*opt)->port);

    if ((*opt)->protocol == PROTOCOL1) {
        free_proto1(*opt);
    }
    else if ((*opt)->protocol == PROTOCOL2) {
        free_proto2(*opt);
    }
    else
        free((*opt)->protocol_data);

    free(*opt);
    return 1;
}

内存是否实际释放,由 return 值指示。

@RobertSsupportsMonicaCellio 和@Federico 提出了一些防止内存 free'ing 两次的可靠方法。但我觉得@vll 提出的建议不应该在评论中丢失,因此我将海报建议总结为答案。

另一种跟踪指针的可靠方法是维护已分配地址的 list,并且仅当它们在列表中时才释放它们。下面是最简单的实现:

#include "list.h"

static struct list addr_list;

struct opts* create_opts(void) {

    struct opts* opts = (struct opts*) calloc(1, sizeof(struct opts));

    if (opts == NULL)
        return NULL;

    /* Initialize the opts{} */

    list_append(&addr_list, opts);

    return opts;
}

int free_opts(struct opts* opts) {

    if (in_list(&addr_list, opts) == false)
        return 0;

    /* Free the opts{} */

    list_remove(&addr_list, opts);

    return 1;
}

当然,上面的实现只是简单地拒绝了释放structs的请求,而structs并不是使用create_opts()创建的。一个部分解决方案是使用标志来启用强制清理。但我希望,如果可能的话,有人会给出一些具体的答案。

感谢大家的宝贵建议:)