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

时间:2016-01-07 09:15:18

标签: c memory free

释放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会更好,但我不确定以下内容:

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

  2. 将整个块设置为0以强制程序崩溃或至少输出结果存在显着差异是不是一个好主意?

5 个答案:

答案 0 :(得分:67)

  

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

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

  

将整个块设置为0以强制执行此操作确实是一个好主意   程序崩溃或至少输出结果显着   差异?

没有。完全没有。

你的同事建议的解决方案很糟糕。这就是原因:

  • 将整个块设置为0也无法实现。因为有人意外地使用了free()块,所以他们根据块的值不知道。这是块calloc()返回的那种。 因此,无法知道它是新分配的内存(calloc()还是malloc()+memset())还是已经被你自由分配的内存(){> 1}代码更早。如果有的话,你的程序需要额外的工作来清空所有被释放的内存块。

  • free(NULL);定义明确且无需操作,因此if中的if(ptr) {free(ptr);}条件无效。

  • 由于free(NULL);是无操作,将指针设置为NULL实际上会隐藏该错误,因为如果某个函数实际上在已经空闲的({1}}上调用指针,那么他们就不会知道。

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

  
NULL

因此,所有这些额外的检查和归零都会给人一种虚假的“稳健感”。虽然它并没有真正改善任何事情。它只是将另一个问题替换为性能和代码膨胀的额外成本。

因此,在没有任何包装函数的情况下调用void do_some_work(void *ptr) { if (!ptr) { return; } /*Do something with ptr here */ } 既简单又健壮(大多数free(ptr);实现会在双重释放时立即崩溃,这是一个良好的事物。)< / p>

没有简单的方法&#34;意外地&#34;致电malloc()两次或更多次。程序员有责任跟踪所有已分配的内存并适当地free()。如果有人发现这很难处理,那么C可能不适合他们。

答案 1 :(得分:9)

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

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

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内)就足够了。如果Foo具有在释放内存后可能被错误访问的额外属性,memset(tmpFoo, 0, sizeof(Foo));将防止崩溃。

所以我会说是的,这样做可能是一个很好的做法......但它只是对那些有不良做法的开发人员的一种安全性(因为它确实存在)没有理由在没有重新分配的情况下拨打DestroyFoo两次) ...最后,您可以DestroyFoo&#34;更安全&#34;但速度较慢(它会更多地阻止它的使用)。

答案 2 :(得分:4)

第二种解决方案似乎过度设计。当然在某些情况下它可能更安全,但开销和复杂性太大了。

如果你想要安全起见,你应该做的是在释放内存后将指针设置为NULL。这总是一个很好的做法。

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;

更重要的是,我不知道为什么人们在调用free()之前检查指针是否为NULL。这不是必需的,因为free()会为你完成这项工作。

将内存设置为0(或其他内容)仅在某些情况下是一种很好的做法,因为free()不会清除内存。它只会将内存区域标记为空闲,以便可以重复使用。如果要清除内存,以便没有人能够读取它,则需要手动清理它。但这是一个非常繁重的操作,这就是为什么不应该用来释放所有内存的原因。在大多数情况下,在没有清理的情况下自由行动就足够了,你不必牺牲性能来做不必要的操作。

答案 3 :(得分:1)

void destroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL;
    memset(tmpFoo, 0, sizeof(Foo));
    free(tmpFoo);
}

您的同事代码不好,因为

  • 如果fooNULL
  • ,则会崩溃
  • 创建额外的变量
  • 没有意义
  • 将值设置为零
  • 没有意义
  • 如果包含必须释放的内容
  • ,则直接释放结构不起作用

我认为你的同事可能想到的是这个用例

Foo* a = NULL;
Foo* b = createFoo();

destroyFoo(NULL);
destroyFoo(&a);
destroyFoo(&b);

在这种情况下,它应该是这样的。 try here

void destroyFoo(Foo** foo)
{
    if (!foo || !(*foo)) return;
    free(*foo);
    *foo = NULL;
}

首先我们需要看看Foo,让我们假设它看起来像这样

struct Foo
{
    // variables
    int number;
    char character;

    // array of float
    int arrSize;
    float* arr;

    // pointer to another instance
    Foo* myTwin;
};

现在要定义它应该如何销毁,让我们首先定义它应该如何被创建

Foo* createFoo (int arrSize, Foo* twin)
{
    Foo* t = (Foo*) malloc(sizeof(Foo));

    // initialize with default values
    t->number = 1;
    t->character = '1';

    // initialize the array
    t->arrSize = (arrSize>0?arrSize:10);
    t->arr = (float*) malloc(sizeof(float) * t->arrSize);

    // a Foo is a twin with only one other Foo
    t->myTwin = twin;
    if(twin) twin->myTwin = t;

    return t;
}

现在我们可以写一个与创建函数

相对的destroy函数
Foo* destroyFoo (Foo* foo)
{
    if (foo)
    {
        // we allocated the array, so we have to free it
        free(t->arr);

        // to avoid broken pointer, we need to nullify the twin pointer
        if(t->myTwin) t->myTwin->myTwin = NULL;
    }

    free(foo);

    return NULL;
}

测试try here

int main ()
{
    Foo* a = createFoo (2, NULL);
    Foo* b = createFoo (4, a);

    a = destroyFoo(a);
    b = destroyFoo(b);

    printf("success");
    return 0;
}

答案 4 :(得分:0)

不幸的是,这个想法是行不通的。

如果意图是要获得双重释放,则不包括以下情况。

假设此代码:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
free (ptr_1);
free (ptr_2); /* This is a bug */

建议改写为:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
DestroyFoo (&ptr_1);
DestroyFoo (&ptr_2); /* This is still a bug */

问题在于,对DestroyFoo()的第二次调用仍然会崩溃,因为ptr_2并未重置为NULL,并且仍然指向已释放的内存。