当前位置:  开发笔记 > 编程语言 > 正文

这是一个释放记忆的好方法吗?

如何解决《这是一个释放记忆的好方法吗?》经验,为你挑选了2个好方法。

释放实例的函数struct Foo如下:

void DestroyFoo(Foo* foo)
{
    if (foo) free(foo);
}

我的一位同事建议如下:

void DestroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL; // prevents future concurrency problems
    memset(tmpFoo, 0, sizeof(Foo));  // problems show up immediately if referred to free memory
    free(tmpFoo);
}

我看到NULL在释放后将指针设置为更好,但我不确定以下内容:

    我们真的需要将指针分配给临时指针吗?它在并发性和共享内存方面有帮助吗?

    将整个块设置为0以强制程序崩溃或至少输出具有显着差异的结果真的是一个好主意吗?

P.P... 67

我们真的需要将指针分配给临时指针吗?它在并发性和共享内存方面有帮助吗?

它与并发或共享内存无关.这是毫无意义.

将整个块设置为0以强制程序崩溃或至少输出具有显着差异的结果真的是一个好主意吗?

一点都不.

你的同事建议的解决方案很糟糕.原因如下:

将整个块设置为0也没有任何结果.因为有人正在意外地使用free()'ed块,他们根据块的值不知道这个.这就是那种块calloc()回报.因此,不可能知道它是新分配的内存(calloc()malloc()+memset())还是之前由您的代码释放的内存().如果有的话,你的程序需要额外的工作来清空每个被释放的内存块().

free(NULL);是明确的,是一个无操作,所以if条件if(ptr) {free(ptr);}没有实现.

既然free(NULL);是no-op,设置指针NULL实际上会隐藏那个bug,因为如果某个函数实际调用free()已经free()'ed指针,那么他们就不会知道.

大多数用户函数在开始时都会进行NULL检查,并且可能不会考虑将NULL其作为错误条件传递给它:

void do_some_work(void *ptr) {
    if (!ptr) {
        return; 
    }

   /*Do something with ptr here */
}

因此,所有这些额外的检查和归零都给人一种虚假的"健壮性"感,而它并没有真正改善任何东西.它只是将另一个问题替换为性能和代码膨胀的额外成本.

所以只是打电话free(ptr);没有任何包装的功能是既简单又强大的(大多数malloc()的实现将立即崩溃双免费的,这是一个很好的事情).

"意外"呼叫free()两次或更多次是没有简单的方法.程序员有责任跟踪分配的所有内存并free()适当地跟踪它.如果有人发现这很难处理,那么C可能不适合他们.



1> P.P...:

我们真的需要将指针分配给临时指针吗?它在并发性和共享内存方面有帮助吗?

它与并发或共享内存无关.这是毫无意义.

将整个块设置为0以强制程序崩溃或至少输出具有显着差异的结果真的是一个好主意吗?

一点都不.

你的同事建议的解决方案很糟糕.原因如下:

将整个块设置为0也没有任何结果.因为有人正在意外地使用free()'ed块,他们根据块的值不知道这个.这就是那种块calloc()回报.因此,不可能知道它是新分配的内存(calloc()malloc()+memset())还是之前由您的代码释放的内存().如果有的话,你的程序需要额外的工作来清空每个被释放的内存块().

free(NULL);是明确的,是一个无操作,所以if条件if(ptr) {free(ptr);}没有实现.

既然free(NULL);是no-op,设置指针NULL实际上会隐藏那个bug,因为如果某个函数实际调用free()已经free()'ed指针,那么他们就不会知道.

大多数用户函数在开始时都会进行NULL检查,并且可能不会考虑将NULL其作为错误条件传递给它:

void do_some_work(void *ptr) {
    if (!ptr) {
        return; 
    }

   /*Do something with ptr here */
}

因此,所有这些额外的检查和归零都给人一种虚假的"健壮性"感,而它并没有真正改善任何东西.它只是将另一个问题替换为性能和代码膨胀的额外成本.

所以只是打电话free(ptr);没有任何包装的功能是既简单又强大的(大多数malloc()的实现将立即崩溃双免费的,这是一个很好的事情).

"意外"呼叫free()两次或更多次是没有简单的方法.程序员有责任跟踪分配的所有内存并free()适当地跟踪它.如果有人发现这很难处理,那么C可能不适合他们.


我同意这个答案,除了关于之后没有将指针设置为NULL的部分.使用NULL分配双重自由至少是一致的.如果没有它,UB的双重免费结果,它**可能会无声地失败**.分配给指针释放的NULL至少可以在以后检测到.不分配NULL只会给出错误的安全感(你预计会发生崩溃).
你的一些评论是对应的.因为在"使用ptr做某事"之前检查指针是否为NULL是一个好习惯,所以在释放后应将ptr设置为NULL.当你释放内存时,这将节省大量花在调试可怕错误上的时间,而不是将ptr设置为NULL,然后其他人将(重新)分配相同的内存区域.这也将防止双重释放,因为您将无法释放设置为NULL的ptr.
大多数用户函数可能在开始时进行NULL检查,但只是快速崩溃(通常只在调试模式下):如果你不能依赖合同后的调用者,你已经输了,因为有太多不可测试他可以履行职责的方式.请参阅[对每个取消引用的指针进行无效保护是否合理?](http://programmers.stackexchange.com/questions/216289/is-it-reasonable-to-null-guard-every-single-dereferenced-pointer)是的,它明确地是大多数释放函数的合同的一部分,以接受NULL或一些其他合适的"非对象"标记.

2> jpo38..:

如果函数被调用两次,你的同事建议将使代码"更安全"(参见sleske评论......因为"更安全"对每个人来说可能并不一样...... ;-).

使用您的代码,这很可能会崩溃:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
DestroyFoo(foo); // will call free on memory already freed

使用您同事的代码版本,这不会崩溃:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(&foo);
DestroyFoo(&foo); // will have no effect

现在,对于这个特定的场景,做tmpFoo = 0;(内DestroyFoo)就足够了.memset(tmpFoo, 0, sizeof(Foo));如果Foo具有在释放内存后可能被错误访问的额外属性,则会阻止崩溃.

所以我会说是的,这样做可能是一个很好的做法....但它只是一种安全措施,对付有不良做法的开发人员(因为没有理由在DestroyFoo没有重新分配的情况下调用两次) ...结束,你使DestroyFoo"更安全"但更慢(它做更多的东西,以防止它的不良使用).


实际上,我会说它通过隐藏bug来降低代码的安全性.如果你意外地双重释放,你_want_它会崩溃.请注意,双重释放不能保证崩溃 - 这是最好的情况.它也可能破坏你的内存分配器并覆盖你所有的东西(未定义的行为).
是的,如果您决定明确支持双重释放,那么根据定义它不是错误; 然后你需要一个包装器来允许它.但这对我来说似乎是一个有问题的设计; 应该有一个特定的点,一个对象被处置.即使在这种情况下,建立的方法是在释放指针后使指针为空(因为"free"对空指针不起作用).
@sleske,jpo38:双重释放已分配的对象很可能意味着你在第一个"free"之后仍然使用它.这是未定义的行为.一旦你释放了一个对象,你就不能再访问它了.如果您使用某种垃圾收集,那么情况会有所不同,如果没有其他参考,您只能免费使用.但是为此你需要额外的措施,比如参考计数器(参见Python的例子).如果**不能**访问对象,你仍然只能调用`free`.
推荐阅读
牛尾巴2010
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有