这是一个在C中释放内存的好方法吗?
释放struct Foo实例的函数如下所示:
void DestroyFoo(Foo* foo) { if (foo) free(foo); }
我的一位同事build议改为:
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以强制程序崩溃或至less输出有显着差异的结果是一个好主意吗?
提前致谢!
我们是否真的需要将指针分配给临时指针? 它在并发和共享内存方面有帮助吗?
它没有任何并发或共享内存。 没有用。
将整个块设置为0以强制程序崩溃或至less输出有显着差异的结果是一个好主意吗?
一点都不。
你的同事提出的解决scheme是可怕的。 原因如下:
-
将整个块设置为0也不能实现。 因为有人不小心使用了免费()的ed块,所以他们根据块的值不会知道这个值。 这就是
calloc()
返回的那种块。 因此,不可能知道它是否是刚分配的内存(calloc()
或malloc()+memset()
),或者是之前由代码自由分配的内存。 如果有的话,你的程序需要额外的工作来清空每个被释放的内存块。 -
free(NULL);
是明确的,是一个无操作,所以if(ptr) {free(ptr);}
的if
条件什么也得不到。 -
由于
free(NULL);
是空操作,将指针设置为NULL
实际上会隐藏该错误,因为如果某个函数实际上在已经释放()的ed指针上调用free()
,那么他们就不会知道这一点。 -
大多数用户函数会在开始时进行NULL检查,可能不会考虑将
NULL
传递给它作为错误条件:
void do_some_work(void *ptr) { if (!ptr) { return; } /*Do something with ptr here */ }
因此,所有这些额外的检查和归零都给人一种“健壮性”的假感,而这并没有真正改善任何事情。 它只是用另一个性能和代码膨胀成本代替了另一个问题。
所以只需拨打free(ptr);
没有任何包装函数是简单和强大的(大多数malloc()
实现将崩溃立即双倍免费,这是一件好事)。
free()
两次或更多的“ free()
”调用没有简单的方法。 程序员有责任跟踪所有分配的内存,并适当地free()
。 如果有人觉得这很难处理,那么C可能不是他们正确的语言。
你的同事提出的build议将使代码“更安全”,以防函数被调用两次(参见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
“更安全”,但速度更慢(它做了更多的东西,以防止使用不当)。
第二个解决scheme似乎是过度devise的。 当然在某些情况下可能会更安全,但是开销和复杂性太大了。
如果你想保证安全,你应该做的是在释放内存后将指针设置为NULL。 这总是一个很好的做法。
Foo* foo = malloc( sizeof(Foo) ); DestroyFoo(foo); foo = NULL;
更重要的是,我不知道为什么人们在调用free()之前检查指针是否为NULL。 这是不需要的,免费()将为你做这项工作。
将内存设置为0(或其他)只是在某些情况下,一个好的做法是free()不会清除内存。 它只会标记一个内存区域,使其可以被重用。 如果你想清除内存,以便没有人能够阅读它,你需要手动清理。 但这是相当繁重的操作,这就是为什么这不应该被用来释放所有的内存。 在大多数情况下,没有清除的情况下释放就够了,你不必牺牲性能来做不必要的操作。
void destroyFoo(Foo** foo) { if (!(*foo)) return; Foo *tmpFoo = *foo; *foo = NULL; memset(tmpFoo, 0, sizeof(Foo)); free(tmpFoo); }
你的同事代码不好,因为
- 如果
foo
为NULL
它将会崩溃 - 创build额外的variables没有意义
- 将值设置为零是没有意义的
- 如果它包含必须被释放的东西,直接释放一个结构不起作用
我想你的同事可能想到的就是这个用例
Foo* a = NULL; Foo* b = createFoo(); destroyFoo(NULL); destroyFoo(&a); destroyFoo(&b);
在这种情况下,应该是这样的。 在这里尝试
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; };
现在来定义它应该如何销毁,我们首先定义它应该如何创build
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; }
现在我们可以写一个与创build函数相反的破坏函数
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; }
testing在这里尝试
int main () { Foo* a = createFoo (2, NULL); Foo* b = createFoo (4, a); a = destroyFoo(a); b = destroyFoo(b); printf("success"); return 0; }