在释放它们之后真的应该将指针设置为"NULL"吗?

Geo*_*lly 48 c free pointers

似乎有两个论点为什么人们应该NULL在释放它们之后设置指针.

双重释放指针时避免崩溃.

简短:free()不小心召唤第二次,当它被设置为时,不会崩溃NULL.

  • 几乎总是这掩盖了一个逻辑错误,因为没有理由free()第二次调用.让应用程序崩溃并能够修复它会更安全.

  • 它不能保证崩溃,因为有时在同一地址分配新内存.

  • 当有两个指向同一地址的指针时,主要发生双重自由.

逻辑错误也可能导致数据损坏.

避免重用已释放的指针

短:malloc()除非释放指针设置为,否则访问释放的指针可能会导致数据损坏,如果在同一位置分配内存NULL

  • NULL如果偏移足够大(someStruct->lastMember,theArray[someBigNumber]),则无法保证在访问指针时程序崩溃.而不是崩溃将导致数据损坏.

  • 将指针设置为NULL无法解决具有相同指针值的不同指针的问题.

问题

这是一篇反对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,具体取决于先前的一些逻辑.而不是if(p)free(p); 你可以使用免费(p).它是C标准的一部分,我会保留这个惯例IMO. (2认同)
  • 我真的不应该发布我在公司薪水时写的代码.它属于支付我工资的公司.但我可以从头开始写同样的事情作为例子,我现在就在答案中做到这一点. (2认同)

Ste*_*sop 8

我不这样做.我特别记得如果我这样做会更容易处理的任何错误.但这实际上取决于你如何编写代码.大约有三种情况我可以解放任何东西:

  • 当持有它的指针即将超出范围,或者是即将超出范围或被释放的对象的一部分时.
  • 当我用新的对象替换对象时(例如,重新分配).
  • 当我释放一个任选存在的对象时.

在第三种情况下,将指针设置为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)

如果在系统上找不到任何此类常量,则可以分配不可读和/或不可写的页面,并使用该地址.

  • 我的代码包含很多在`#ifdef DEBUG`下编译的东西,所以我的DEBUG构建非常小心,并且发布版本没有减慢.我的DEBUG构建使用0xDC字节填充MALLOC分配的所有内存; 0xFE也会起作用.在释放结构之前,DEBUG构建用0xDC填充结构,并且在释放之后它将指针设置为NULL.一次或两次我的理智检查断言已经解雇,因为我有一个指向我释放的内存的指针,并且在free上覆盖数据导致理智检查失败.这比在调试器中花费数小时要好得多. (2认同)

sha*_*oth 1

两者都非常重要,因为它们处理未定义的行为。您不应该在程序中留下任何未定义行为的方式。两者都可能导致崩溃、数据损坏、细微错误或任何其他不良后果。

两者都很难调试。两者都是不可避免的,尤其是在复杂数据结构的情况下。无论如何,如果您遵循以下规则,您的情况会好得多:

  • 始终初始化指针 - 将它们设置为 NULL 或某个有效地址
  • 调用 free() 后将指针设置为 NULL
  • 在取消引用任何可能为 NULL 的指针之前,检查它们是否实际上为 NULL。

  • 是的,有些情况下它没有帮助。但如果你总是留下悬空指针,情况会变得更糟。如您所知,安全带并不能保证一个人在车祸中幸存,但这并不意味着安全带完全没有用。 (3认同)
  • _为什么?_,这篇文章http://stackoverflow.com/questions/1025589/setting-variable-to-null-after-free/1879377#1879377声称设置指针指向“NULL”通常没有帮助。 (2认同)