似乎有两个论点为什么人们应该NULL
在释放它们之后设置指针.
简短:free()
不小心召唤第二次,当它被设置为时,不会崩溃NULL
.
几乎总是这掩盖了一个逻辑错误,因为没有理由free()
第二次调用.让应用程序崩溃并能够修复它会更安全.
它不能保证崩溃,因为有时在同一地址分配新内存.
当有两个指向同一地址的指针时,主要发生双重自由.
逻辑错误也可能导致数据损坏.
短:malloc()
除非释放指针设置为,否则访问释放的指针可能会导致数据损坏,如果在同一位置分配内存NULL
NULL
如果偏移足够大(someStruct->lastMember
,theArray[someBigNumber]
),则无法保证在访问指针时程序崩溃.而不是崩溃将导致数据损坏.
将指针设置为NULL
无法解决具有相同指针值的不同指针的问题.
随意扩展这个问题.
ste*_*eha 25
第二个更重要:重用已释放的指针可能是一个微妙的错误.您的代码保持正常工作,然后崩溃没有明确的原因,因为一些看似无关的代码在内存中写入了重用指针恰好指向的内容.
我曾经不得不在别人写的一个非常错误的程序上工作.我的直觉告诉我,许多错误都与在释放内存后继续使用指针的草率尝试有关; 我修改了代码,在释放内存后将指针设置为NULL,而bam,空指针异常开始到来.之后我定的所有空指针异常,突然码是多少更稳定.
在我自己的代码中,我只调用我自己的函数,它是free()的包装器.它需要一个指向指针的指针,并在释放内存后使指针为空.在它调用free之前,它调用Assert(p != NULL);
它仍然捕获尝试双重释放相同的指针.
我的代码也执行其他操作,例如(仅在DEBUG构建中)在分配之后立即填充具有明显值的内存,在调用之前执行相同操作free()
以防有指针的副本,等等. 此处详细说明.
编辑:根据请求,这是示例代码.
void
FreeAnything(void **pp)
{
void *p;
AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
if (!pp)
return;
p = *pp;
AssertWithMessage(p != NULL, "attempt to free a null pointer");
if (!p)
return;
free(p);
*pp = NULL;
}
// FOO is a typedef for a struct type
void
FreeInstanceOfFoo(FOO **pp)
{
FOO *p;
AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
if (!pp)
return;
p = *pp;
AssertWithMessage(p != NULL, "attempt to free a null FOO pointer");
if (!p)
return;
AssertWithMessage(p->signature == FOO_SIG, "bad signature... is this really a FOO instance?");
// free resources held by FOO instance
if (p->storage_buffer)
FreeAnything(&p->storage_buffer);
if (p->other_resource)
FreeAnything(&p->other_resource);
// free FOO instance itself
free(p);
*pp = NULL;
}
Run Code Online (Sandbox Code Playgroud)
评论:
您可以在第二个函数中看到我需要检查两个资源指针以查看它们是否为空,然后调用FreeAnything()
.这是因为assert()
它会抱怨空指针.我有这个断言是为了检测双重释放的尝试,但我不认为它实际上已经为我捕获了许多错误; 如果你想省略断言,那么你可以省略支票并随时打电话FreeAnything()
.除了断言之外,当你尝试释放空指针时没有任何不好的事情发生,FreeAnything()
因为它检查指针并且如果它已经为空则返回.
我的实际函数名称更简洁,但我试图为此示例选择自我记录的名称.另外,在我的实际代码中,我只有调试代码,0xDC
在调用之前用缓冲区填充缓冲区,free()
这样如果我有一个指向同一个内存的额外指针(一个没有被淘汰的内存),那么数据就显而易见了.它指向的是虚假数据.我有一个宏,DEBUG_ONLY()
在非调试版本中编译为空; 和宏FILL()
,做了sizeof()
一个结构.这两个同样有效:sizeof(FOO)
或sizeof(*pfoo)
.所以这是FILL()
宏:
#define FILL(p, b) \
(memset((p), b, sizeof(*(p)))
Run Code Online (Sandbox Code Playgroud)
下面是使用的例子FILL()
把0xDC
值之前调用:
if (p->storage_buffer)
{
DEBUG_ONLY(FILL(pfoo->storage_buffer, 0xDC);)
FreeAnything(&p->storage_buffer);
}
Run Code Online (Sandbox Code Playgroud)
使用此示例:
PFOO pfoo = ConstructNewInstanceOfFoo(arg0, arg1, arg2);
DoSomethingWithFooInstance(pfoo);
FreeInstanceOfFoo(&pfoo);
assert(pfoo == NULL); // FreeInstanceOfFoo() nulled the pointer so this never fires
Run Code Online (Sandbox Code Playgroud)
我不这样做.我特别记得如果我这样做会更容易处理的任何错误.但这实际上取决于你如何编写代码.大约有三种情况我可以解放任何东西:
在第三种情况下,将指针设置为NULL.这不是特别因为你释放它,这是因为它是可选的,所以当然NULL是一个特殊值,意思是"我没有一个".
在前两种情况下,将指针设置为NULL似乎是忙于工作而没有特殊目的:
int doSomework() {
char *working_space = malloc(400*1000);
// lots of work
free(working_space);
working_space = NULL; // wtf? In case someone has a reference to my stack?
return result;
}
int doSomework2() {
char * const working_space = malloc(400*1000);
// lots of work
free(working_space);
working_space = NULL; // doesn't even compile, bad luck
return result;
}
void freeTree(node_type *node) {
for (int i = 0; i < node->numchildren; ++i) {
freeTree(node->children[i]);
node->children[i] = NULL; // stop wasting my time with this rubbish
}
free(node->children);
node->children = NULL; // who even still has a pointer to node?
// Should we do node->numchildren = 0 too, to keep
// our non-existent struct in a consistent state?
// After all, numchildren could be big enough
// to make NULL[numchildren-1] dereferencable,
// in which case we won't get our vital crash.
// But if we do set numchildren = 0, then we won't
// catch people iterating over our children after we're freed,
// because they won't ever dereference children.
// Apparently we're doomed. Maybe we should just not use
// objects after they're freed? Seems extreme!
free(node);
}
int replace(type **thing, size_t size) {
type *newthing = copyAndExpand(*thing, size);
if (newthing == NULL) return -1;
free(*thing);
*thing = NULL; // seriously? Always NULL after freeing?
*thing = newthing;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
确实,如果你有一个bug,你试图在释放后取消引用它,那么对指针进行NULL操作可以使它变得更加明显.如果你没有指针NULL,解除引用可能不会立即造成伤害,但从长远来看是错误的.
对指针进行NULL操作也会掩盖双重释放的错误.如果你对指针执行NULL,则第二个free不会立即造成伤害,但从长远来看是错误的(因为它背叛了你的对象生命周期被破坏的事实).当你释放它们时,你可以声明它们是非空的,但这导致以下代码释放一个包含可选值的结构:
if (thing->cached != NULL) {
assert(thing->cached != NULL);
free(thing->cached);
thing->cached = NULL;
}
free(thing);
Run Code Online (Sandbox Code Playgroud)
那段代码告诉你的是,你已经走得太远了.它应该是:
free(thing->cached);
free(thing);
Run Code Online (Sandbox Code Playgroud)
我说,如果它应该保持可用,则指针为NULL .如果它不再可用,最好不要通过输入像NULL这样的潜在有意义的值来使它看起来错误.如果您想引发页面错误,请使用一个与平台无关的值,这个值不是dereferancable,但其余代码不会将其视为特殊的"一切都很好,花花公子"值:
free(thing->cached);
thing->cached = (void*)(0xFEFEFEFE);
Run Code Online (Sandbox Code Playgroud)
如果在系统上找不到任何此类常量,则可以分配不可读和/或不可写的页面,并使用该地址.
两者都非常重要,因为它们处理未定义的行为。您不应该在程序中留下任何未定义行为的方式。两者都可能导致崩溃、数据损坏、细微错误或任何其他不良后果。
两者都很难调试。两者都是不可避免的,尤其是在复杂数据结构的情况下。无论如何,如果您遵循以下规则,您的情况会好得多: