释放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以强制程序崩溃或至少输出结果存在显着差异是不是一个好主意?
答案 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);
}
您的同事代码不好,因为
foo
为NULL
我认为你的同事可能想到的是这个用例
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,并且仍然指向已释放的内存。