在释放它们之后,真的应该设置指向NULL的指针吗?
似乎有两个参数为什么在释放它们之后应该将指针设置为NULL
。
当释放双指针时避免崩溃。
简而言之:无意中调用free()
,当它被设置为NULL
时不会崩溃。
几乎总是这样掩盖了一个逻辑错误,因为没有理由再次调用free()
。 让应用程序崩溃并修复它会更安全。
它不能保证崩溃,因为有时候新的内存分配在相同的地址。
当有两个指向相同地址的指针时,主要发生双重空闲。
逻辑错误也可能导致数据损坏。
避免重复使用已释放的指针
简而言之:如果malloc()
在相同的位置分配内存,除非释放的指针设置为NULL
则访问释放的指针可能导致数据损坏
如果偏移量足够大( someStruct->lastMember
, theArray[someBigNumber]
), theArray[someBigNumber]
不能保证程序在访问NULL
指针时崩溃。 而不是崩溃会有数据损坏。
将指针设置为NULL
不能解决具有相同指针值的不同指针的问题。
问题
这里有一篇文章是在释放后盲目地设置一个指向NULL
的指针。
随意扩大这个问题。
第二种方法更重要:重新使用释放的指针可能是一个微妙的错误。 你的代码保持正常工作,然后崩溃没有明确的理由,因为一些看起来不相关的代码在内存中写入重用指针正好指向。
我曾经不得不在其他人写的一个非常错误的程序上工作。 我的直觉告诉我,许多错误都与在释放内存后马虎不离地尝试继续使用指针有关; 我修改了代码以释放内存后将指针设置为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;
}
注释:
你可以在第二个函数中看到我需要检查两个资源指针,看它们是否为空,然后调用FreeAnything()
。 这是因为assert()
会抱怨空指针。 我有这个断言,以检测双免费的尝试,但我不认为它实际上已经抓住了我的许多错误; 如果你想忽略断言,那么你可以忽略支票,只是总是打电话给FreeAnything()
。 除了断言之外,当您尝试释放FreeAnything()
的空指针时没有什么不好,因为它检查指针,如果它已经为空就返回。
我的实际函数名称比较简洁,但我试图为此示例选择自编档名。 另外,在我的实际代码中,我调用free()
之前只有调试代码用0xDC
填充缓冲区,这样如果我有一个额外的指针指向同一个内存(不会被清除),它就变成了真正的显然它指向的数据是虚假数据。 我有一个宏DEBUG_ONLY()
,它在非调试版本上编译为无; 和一个在结构体上执行sizeof()
的宏FILL()
。 这两个工作同样好: sizeof(FOO)
或sizeof(*pfoo)
。 所以这里是FILL()
宏:
#define FILL(p, b)
(memset((p), b, sizeof(*(p)))
以下是调用前使用FILL()
放置0xDC
值的0xDC
:
if (p->storage_buffer)
{
DEBUG_ONLY(FILL(pfoo->storage_buffer, 0xDC);)
FreeAnything(&p->storage_buffer);
}
一个使用这个例子的例子:
PFOO pfoo = ConstructNewInstanceOfFoo(arg0, arg1, arg2);
DoSomethingWithFooInstance(pfoo);
FreeInstanceOfFoo(&pfoo);
assert(pfoo == NULL); // FreeInstanceOfFoo() nulled the pointer so this never fires
我不这样做。 我不特别记得如果我这样做会更容易处理的任何错误。 但这取决于你如何编写代码。 大约有三种情况我可以释放任何东西:
在第三种情况下,您将指针设置为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;
}
确实,如果在释放后尝试对其进行解引用时存在错误,则使指针为NULL可以使其更加明显。 如果您不指定空值,则解引用可能不会造成直接影响,但从长远来看,这是错误的。
将指针置零可以避免双免费的错误,这也是事实。 第二个空闲指针不会造成直接伤害,但是从长远来看是错误的(因为它揭示了对象生命周期被破坏的事实)。 当你释放它们时,你可以声明事物是非空的,但是这会导致下面的代码释放一个包含可选值的结构体:
if (thing->cached != NULL) {
assert(thing->cached != NULL);
free(thing->cached);
thing->cached = NULL;
}
free(thing);
该代码告诉你,是你太过分了。 它应该是:
free(thing->cached);
free(thing);
我说,如果它应该保持可用,则指针为NULL。 如果它不再可用,最好不要使它看起来像是,通过放入一个潜在有意义的值,如NULL。 如果您想引发页面错误,请使用不依赖于平台的平台相关值,但其余代码不会将其视为特殊的“一切都很好,很花哨”值:
free(thing->cached);
thing->cached = (void*)(0xFEFEFEFE);
如果在系统上找不到任何此类常量,则可以分配不可读和/或不可写的页面,并使用该页面的地址。
如果你没有将指针设置为NULL,那么就有一个不小的机会,你的应用程序继续以未定义的状态运行,然后在完全不相关的点上崩溃。 然后,在发现之前,您将花费大量时间调试不存在的错误,这是早期的内存损坏。
我将指针设置为NULL,因为如果你没有将它设置为NULL,那么你的错误发生的位置就会更高。 第二次释放内存的逻辑错误仍然值得考虑,并且您的应用程序不会在具有足够大偏移量的空指针访问时崩溃的错误在我看来是完全学术化的,但并非不可能。
结论:我会去设置指针为NULL。
链接地址: http://www.djcxy.com/p/80397.html上一篇: Should one really set pointers to `NULL` after freeing them?
下一篇: Some memory seems to be left allocated after malloc() and free()