这是释放内存的好方法吗?
Is this a good way to free memory?
释放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 以强制程序崩溃或至少输出具有显着差异的结果真的是个好主意吗?
如果函数被调用两次,您同事的建议将使代码成为 "safer"(请参阅 sleske 评论...因为 "safer" 对每个人来说可能并不相同...;- ).
使用您的代码,这很可能会崩溃:
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
"safer" 但速度较慢(它做了更多的事情以防止它的错误使用)。
第二个解决方案似乎设计过度了。当然在某些情况下它可能更安全但是开销和复杂性太大了。
安全起见,释放内存后将指针设置为NULL。这始终是一个好习惯。
Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;
而且,我不知道为什么人们在调用 free() 之前检查指针是否为 NULL。这不是必需的,因为 free() 会为您完成这项工作。
将内存设置为 0(或其他)仅在某些情况下是一个好习惯,因为 free() 不会清除内存。它只会将内存区域标记为空闲,以便可以重复使用。如果你想清除内存,以便没有人能够读取它,你需要手动清理它。但这是相当繁重的操作,这就是为什么不应该使用它来释放所有内存的原因。在大多数情况下,释放而不清除就足够了,您不必牺牲性能来进行不必要的操作。
Do we really need to assign the pointer to a temporary one? Does it
help in terms of concurrency and shared memory?
它与并发或共享内存无关。没意义。
Is it really a good idea to set the whole block to 0 to force the
program to crash or at least to output results with significant
discrepancy?
没有。完全没有。
您同事建议的解决方案很糟糕。原因如下:
将整个块设置为 0 也没有任何效果。因为有人不小心使用了一个 free() 块,他们不会根据块中的值知道这一点。就是那种块calloc()
returns。
所以不可能知道它是新分配的内存(calloc()
或 malloc()+memset()
)还是之前被您的代码释放的内存。如果有的话,你的程序需要额外的工作来将每个空闲的内存块清零。
free(NULL);
是 well-defined 并且是 no-op,所以 if(ptr) {free(ptr);}
中的 if
条件没有实现。
由于 free(NULL);
是 no-op,将指针设置为 NULL
实际上会隐藏该错误,因为如果某些函数实际上正在调用 free()
在一个已经 free() 的指针上,那么他们就不会知道了。
大多数用户函数在开始时都会进行 NULL 检查,并且可能不会考虑将 NULL
传递给它作为错误条件:
void do_some_work(void *ptr) {
if (!ptr) {
return;
}
/*Do something with ptr here */
}
所以所有那些额外的检查和归零都给人一种假 "robustness" 的感觉,而它并没有真正改善任何东西。它只是用另一个问题代替了一个问题,即性能和代码膨胀的额外成本。
所以只调用 free(ptr);
而没有任何包装函数既简单又健壮(大多数 malloc()
实现会在双重释放时立即崩溃,这是 好 东西)。
"accidentally" 调用 free()
两次或更多没有简单的方法。程序员有责任跟踪所有分配的内存并适当地free()
。如果有人觉得这很难处理,那么 C 语言可能不适合他们。
void destroyFoo(Foo** foo)
{
if (!(*foo)) return;
Foo *tmpFoo = *foo;
*foo = NULL;
memset(tmpFoo, 0, sizeof(Foo));
free(tmpFoo);
}
你的同事代码不好,因为
- 如果
foo
是 NULL
会崩溃
- 创建额外的变量没有意义
- 将值设置为零没有意义
- 如果结构包含必须释放的内容,则直接释放结构不起作用
我想你的同事可能想到的是这个use-case
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;
}
现在我们可以写一个与创建函数相对的销毁函数
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;
}
不幸的是,这个想法行不通。
如果目的是捕获双倍释放,则不包括以下情况。
假设此代码:
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,并且仍然指向已释放的内存。
释放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 以强制程序崩溃或至少输出具有显着差异的结果真的是个好主意吗?
如果函数被调用两次,您同事的建议将使代码成为 "safer"(请参阅 sleske 评论...因为 "safer" 对每个人来说可能并不相同...;- ).
使用您的代码,这很可能会崩溃:
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
"safer" 但速度较慢(它做了更多的事情以防止它的错误使用)。
第二个解决方案似乎设计过度了。当然在某些情况下它可能更安全但是开销和复杂性太大了。
安全起见,释放内存后将指针设置为NULL。这始终是一个好习惯。
Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;
而且,我不知道为什么人们在调用 free() 之前检查指针是否为 NULL。这不是必需的,因为 free() 会为您完成这项工作。
将内存设置为 0(或其他)仅在某些情况下是一个好习惯,因为 free() 不会清除内存。它只会将内存区域标记为空闲,以便可以重复使用。如果你想清除内存,以便没有人能够读取它,你需要手动清理它。但这是相当繁重的操作,这就是为什么不应该使用它来释放所有内存的原因。在大多数情况下,释放而不清除就足够了,您不必牺牲性能来进行不必要的操作。
Do we really need to assign the pointer to a temporary one? Does it help in terms of concurrency and shared memory?
它与并发或共享内存无关。没意义。
Is it really a good idea to set the whole block to 0 to force the program to crash or at least to output results with significant discrepancy?
没有。完全没有。
您同事建议的解决方案很糟糕。原因如下:
将整个块设置为 0 也没有任何效果。因为有人不小心使用了一个 free() 块,他们不会根据块中的值知道这一点。就是那种块
calloc()
returns。 所以不可能知道它是新分配的内存(calloc()
或malloc()+memset()
)还是之前被您的代码释放的内存。如果有的话,你的程序需要额外的工作来将每个空闲的内存块清零。free(NULL);
是 well-defined 并且是 no-op,所以if(ptr) {free(ptr);}
中的if
条件没有实现。由于
free(NULL);
是 no-op,将指针设置为NULL
实际上会隐藏该错误,因为如果某些函数实际上正在调用free()
在一个已经 free() 的指针上,那么他们就不会知道了。大多数用户函数在开始时都会进行 NULL 检查,并且可能不会考虑将
NULL
传递给它作为错误条件:
void do_some_work(void *ptr) {
if (!ptr) {
return;
}
/*Do something with ptr here */
}
所以所有那些额外的检查和归零都给人一种假 "robustness" 的感觉,而它并没有真正改善任何东西。它只是用另一个问题代替了一个问题,即性能和代码膨胀的额外成本。
所以只调用 free(ptr);
而没有任何包装函数既简单又健壮(大多数 malloc()
实现会在双重释放时立即崩溃,这是 好 东西)。
"accidentally" 调用 free()
两次或更多没有简单的方法。程序员有责任跟踪所有分配的内存并适当地free()
。如果有人觉得这很难处理,那么 C 语言可能不适合他们。
void destroyFoo(Foo** foo)
{
if (!(*foo)) return;
Foo *tmpFoo = *foo;
*foo = NULL;
memset(tmpFoo, 0, sizeof(Foo));
free(tmpFoo);
}
你的同事代码不好,因为
- 如果
foo
是NULL
会崩溃
- 创建额外的变量没有意义
- 将值设置为零没有意义
- 如果结构包含必须释放的内容,则直接释放结构不起作用
我想你的同事可能想到的是这个use-case
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;
}
现在我们可以写一个与创建函数相对的销毁函数
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;
}
不幸的是,这个想法行不通。
如果目的是捕获双倍释放,则不包括以下情况。
假设此代码:
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,并且仍然指向已释放的内存。