【问题标题】:Can I have a C routine, which prevents free()ing the memory twice?我可以有一个 C 例程,它可以防止两次释放内存吗?
【发布时间】:2020-09-23 06:54:47
【问题描述】:

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

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

【问题讨论】:

  • 没有办法防止像您描述的情况,您只需记录您的 API 并告诉用户不要愚蠢。如果他们然后做了一些愚蠢的事情,那是他们的错。
  • 是的,只需创建您自己的函数来分配和释放,它会保留有关已分配内存块的索引。
  • #define FREE(A) do { free(A); A = NULL; } while (0)
  • @Someprogrammerdude 我并不完全了解 Linux 内核如何管理内存中的进程,但我相信它会将内存分配的信息存储在某处。如果它存储了,我们不能在执行 free()ing 操作之前访问它吗?
  • @qrdl 这是一个非常方便的宏,但如果结构有我展示的指针,您可能会依赖垃圾收集器来释放该内存。而且,我担心的是,当再次调用相同的 FREE(B) (B = A) 时,程序会崩溃。

标签: c linux pointers memory-management free


【解决方案1】:

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

在 C 语言中,您必须始终小心不要导致内存泄漏。


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

只有在 所有 引用都被“伪释放”时才会释放内存。

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

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

内存是否真正被释放,由返回值表示。

【讨论】:

    【解决方案2】:

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

    另一种跟踪指针的可靠方法是维护分配地址的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的请求,它们不是使用create_opts()创建的。对此的部分解决方案是使用标志来启用强制清理。但我希望,如果可能的话,有人会给出一些具体的答案。

    感谢大家提出的宝贵建议:)

    【讨论】:

      【解决方案3】:

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

      struct opts* ref_opts(struct opts* opt)
      

      将增加 ref 计数器并返回 opt 的 API;最后,将 free_opts 重命名为 unref_opts() 并且仅在 refs 字段为 0 时才真正释放您的结构。

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

      【讨论】:

        【解决方案4】:

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

        那么即使你释放了不止一次 - free 会忽略 NULL 指针。

        【讨论】:

        • 我认为您没有很好地理解我的问题,或者您从问题标题中得出结论:(。我确实将指针设置为 NULL,但是如果在另一个指向的指针上调用 free()被释放的地址,这是我的场景。
        • 一种解决方案是使用一个带有指针的删除函数,释放指针的值并将该值设置为空。
        • @MohithReddy 这是糟糕的软件设计的标志。除了改变你的编程方式之外,没有办法让你免于糟糕的编程。
        猜你喜欢
        • 2018-07-15
        • 2012-08-11
        • 2015-06-26
        • 2015-07-25
        • 1970-01-01
        • 2020-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多