免费后将变量设置为NULL

Alp*_*neo 146 c malloc free coding-style heap-memory

在我的公司中,有一个编码规则,在释放任何内存后,将变量重置为NULL.例如 ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}
Run Code Online (Sandbox Code Playgroud)

我觉得,在上面显示的代码中,设置为NULL没有任何意义.或者我错过了什么?

如果在这种情况下没有任何意义,我将采用"质量团队"来删除此编码规则.请指教.

Mar*_*wis 278

将未使用的指针设置为NULL是一种防御方式,可防止悬空指针错误.如果在释放后访问悬空指针,则可以读取或覆盖随机存储器.如果访问空指针,则会在大多数系统上立即崩溃,立即告诉您错误是什么.

对于局部变量,如果在释放后指针不再被访问是"显而易见的",那么它可能有点无意义,因此这种样式更适合于成员数据和全局变量.即使对于局部变量,如果在释放内存后函数继续,也可能是一个好方法.

要完成样式,还应该在为指定真值指针值之前将指针初始化为NULL.

  • @Paul:在特定情况下,声明可以读为`int*nPtr = NULL;`.现在,我同意这将是多余的,在下一行中跟随malloc.但是,如果声明和第一次初始化之间存在代码,则有人可能会开始使用该变量,即使它还没有值.如果你null-initialize,你会得到段错误; 没有,你可能会再次读或写随机内存.同样,如果变量稍后只是有条件地初始化,那么后来的错误访问应该会让你在忘记空初始化时立即崩溃. (25认同)
  • Wilhelm,关键在于使用空指针取消引用会得到确定的崩溃和问题的实际位置.糟糕的访问可能会或可能不会崩溃,并在意外的地方以意外的方式破坏数据或行为. (8认同)
  • 实际上,将指针初始化为NULL至少有一个明显的缺点:它可以阻止编译器向您发出有关未初始化变量的警告.除非代码的逻辑实际上显式地处理指针的值(即if(nPtr == NULL)dosomething ...),否则最好保持原样. (4认同)
  • 我不明白你为什么要“在指针被分配一个真正的指针值之前将指针初始化为 NULL”? (3认同)

AnT*_*AnT 36

设置指向NULLafter 的指针free是一种可疑的做法,通常在明显错误的前提下被普及为"良好编程"规则.这是属于"声音正确"类别的那些假真相之一,但实际上绝对没有任何用处(有时会导致负面后果).

据称,当指针值传递给多次时,设置指向NULLafter 的指针free应该可以防止可怕的"双重自由"问题free.但实际上,在10个中的9个案例中,当具有相同指针值的不同指针对象用作参数时,会发生真正的"双重自由"问题free.不用说,设置指针指向NULLfree达到绝对没有以防止此类案件的问题.

当然,当使用相同的指针对象作为参数时,可能会遇到"双重自由"问题free.然而,实际上这种情况通常表明代码的一般逻辑结构存在问题,而不仅仅是意外的"双重自由".在这种情况下处理问题的正确方法是检查并重新考虑代码的结构,以避免在同一指针被free多次传递时的情况.在这种情况下,设置指针NULL并将问题视为"固定"只不过是试图扫除地毯下的问题.它在一般情况下根本不起作用,因为代码结构的问题总是会找到另一种表现方式.

最后,如果你的代码专门设计为依赖于指针值,那么将指针值设置为after 就完全NULLNULL了问题.但作为一个普遍的"良好实践"规则(如"总是把你的指针设置在后面"),它再一次是一个众所周知的,相当无用的假货,往往是一些纯粹的宗教,巫术般的原因.NULLfreeNULLfree

  • @AnT"可疑"有点多了.这一切都取决于用例.如果指针的值曾被用于真/假意义,那么它不仅是一种有效的实践,而且是最佳实践. (4认同)
  • 我认为主要的好处不是防止双重释放,而是更早、更可靠地捕获悬空指针。例如,当释放保存资源、指向已分配内存的指针、文件句柄等的结构时,当我释放所包含的内存指针并关闭所包含的文件时,我将相应的成员设置为 NULL。然后,如果错误地通过悬空指针访问了其中一个资源,则程序每次都会在那里出错。否则,如果没有 NULLing,释放的数据可能还不会被覆盖,并且错误可能不容易重现。 (3认同)
  • 我同意结构良好的代码不应该允许指针在被释放后被访问或被释放两次的情况。但在现实世界中,我的代码将被可能不认识我并且没有时间和/或技能来正确做事的人修改和/或维护(因为截止日期总是昨天)。因此,我倾向于编写即使被误用也不会使系统崩溃的防弹函数。 (2认同)

小智 32

大多数响应都集中在防止双重释放,但将指针设置为NULL还有另一个好处.释放指针后,可以通过另一次调用malloc来重新分配该内存.如果你仍然有原始指针你可能最终得到一个错误,你试图在释放后使用指针并破坏其他变量,然后你的程序进入一个未知状态,可能发生各种坏事(如果你崩溃"幸运的是,如果你运气不好,数据就会被破坏".如果在释放后将指针设置为NULL,则稍后通过该指针读取/写入的任何尝试都将导致段错误,这通常比随机内存损坏更可取.

出于这两个原因,在free()之后将指针设置为NULL是个好主意.但这并不总是必要的.例如,如果指针变量在free()之后立即超出范围,则没有太多理由将其设置为NULL.

  • @DavidSchwartz:我不同意你的评论。几周前,当我不得不为大学练习编写堆栈时,我遇到了一个问题,我调查了几个小时。我在某个时候访问了一些已经释放的内存(免费是一些行太早了)。有时它会导致非常奇怪的行为。如果我在释放指针后将其设置为 NULL,则会出现“简单”的段错误,我将节省几个小时的工作。所以这个答案+1! (2认同)
  • @katze_sonne即使停止的时钟也是一天两次.设置指向NULL的指针更有可能通过防止对已经释放的对象的错误访问来阻止错误访问错误的代码来检查NULL,然后无声地检查它应该检查的对象.(也许在特定的调试版本中释放后将指针设置为NULL可能会有所帮助,或者将它们设置为保证为segfault的NULL以外的值可能是有意义的.但是这种愚蠢的事情恰好可以帮助你*不是*参数它的好处.) (2认同)

raz*_*zed 19

这被认为是避免覆盖内存的好习惯.在上面的函数中,它是不必要的,但通常在完成时它可以找到应用程序错误.

尝试这样的事情:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif
Run Code Online (Sandbox Code Playgroud)

DEBUG_VERSION允许您在调试代码中分析释放,但两者在功能上是相同的.

编辑:添加了...,如下所示,谢谢.

  • 宏不应该在括号中,它应该在`do {} while(0)`块中,以便`if(x)myfree(x); 否则dostuff();`不会破坏. (5认同)
  • 如果在没有括号的if语句之后使用它,则宏版本有一个微妙的错误. (3认同)
  • 正如Lutz所说,宏体'do {X}而(0)`是IMO最好的方法来制作一个"感觉就像一个功能"的宏体.无论如何,大多数编译器都会优化循环. (3认同)

Jar*_*aus 7

将指针设置为free'd存储器意味着通过指针访问该存储器的任何尝试都将立即崩溃,而不是导致未定义的行为.它可以更容易地确定出错的地方.

我可以看到你的论点:因为nPtr它刚刚超出范围nPtr = NULL,似乎没有理由将它设置为NULL.但是,struct对于指针不会立即超出范围的成员或其他位置,它更有意义.目前还不清楚该指针是否会被不应该使用它的代码再次使用.

可能会在不对这两种情况进行区分的情况下声明规则,因为自动执行规则要困难得多,更不用说让开发人员遵循规则了.NULL在每次免费之后设置指针并没有什么坏处,但它有可能指出大问题.


Tad*_*ski 7

如果你到达了free()d的指针,它可能会破坏.该内存可能会重新分配给您的程序的另一部分,然后您会得到内存损坏,

如果将指针设置为NULL,那么如果您访问它,程序将始终崩溃并出现段错误.没有更多,有时候它会起作用',不再是,以不可预测的方式崩溃''.它更容易调试.

  • 该程序并不总是崩溃与段错误.如果访问指针的方式意味着在解除引用之前对其应用了足够大的偏移量,那么它可能会到达可寻址的内存:((MyHugeStruct*)0) - > fieldNearTheEnd.甚至在您处理完全没有0访问段的硬件之前就已经存在了.但是,该程序更容易因段错误而崩溃. (5认同)

Rag*_*geZ 7

c中最常见的错误是双重免费.基本上你做那样的事情

free(foobar);
/* lot of code */
free(foobar);
Run Code Online (Sandbox Code Playgroud)

它最终非常糟糕,操作系统尝试释放一些已经释放的内存,通常是段错误.所以好的做法是设置NULL,所以你可以进行测试并检查你是否真的需要释放这些内存

if(foobar != null){
  free(foobar);
}
Run Code Online (Sandbox Code Playgroud)

还要注意的是,free(NULL)不会做任何事情,所以你不必编写if语句.我不是一个真正的操作系统大师,但我现在甚至相当大多数操作系统会在双重免费时崩溃.

这也是为什么所有带垃圾收集的语言(Java,dotnet)为没有这个问题而感到骄傲的主要原因,也没有必要让开发人员整个内存管理.

  • 实际上你可以在不检查的情况下调用free() - free(NULL)被定义为什么都不做. (11认同)
  • 这不是隐藏错误吗?(就像释放太多.) (5认同)
  • 正如我所说,`free(void*ptr)`_cannot_改变传递指针的值.它可以更改指针的_contents_,存储在该address_的_data,但不能更改_address本身_或pointer_的_value.这将需要`free(void**ptr)`(标准显然不允许)或宏(允许且完全可移植,但人们不喜欢宏).此外,C不是为了方便,而是为程序员提供他们想要的控制权.如果他们不想要将指针设置为"NULL"的额外开销,则不应强制它们. (5认同)
  • 世界上很少有东西可以消除C代码作者的一部分缺乏专业性.但它们包括"在调用`free`之前检查指针是否为NULL"(以及"转换内存分配函数的结果"或"使用`sizeof`类型名称无意义地使用"等). (2认同)

Mit*_*eat 6

这背后的想法是阻止意外重用释放的指针.


小智 6

最近我在寻找答案后遇到了同样的问题。我得出这样的结论:

这是最佳实践,必须遵循这一做法才能使其在所有(嵌入式)系统上可移植。

free()是一个库函数,它会随着平台的变化而变化,因此您不应期望在将指针传递给该函数并释放内存后,该指针将被设置为 NULL。对于为该平台实现的某些库来说,情况可能并非如此。

所以总是追求

free(ptr);
ptr = NULL;
Run Code Online (Sandbox Code Playgroud)


Ste*_*eld 5

这实际上很重要。尽管您释放了内存,但程序的后续部分可能会分配恰好落在该空间中的新内容。您的旧指针现在将指向有效的内存块。那么有人可能会使用该指针,从而导致无效的程序状态。

如果您将指针设为 NULL,那么任何使用它的尝试都会取消引用 0x0 并在那里崩溃,这很容易调试。指向随机内存的随机指针很难调试。这显然是没有必要的,但这就是为什么它出现在最佳实践文档中。

  • jeffamaphone,当您再次使用指针时,删除的内存块可能已被重新分配并分配给*另一个*对象。 (2认同)

Vij*_*hew 5

来自 ANSI C 标准:

void free(void *ptr);
Run Code Online (Sandbox Code Playgroud)

free 函数导致 ptr 指向的空间被释放,即可供进一步分配。如果 ptr 是空指针,则不会发生任何操作。否则,如果参数与 calloc 、 malloc 或 realloc 函数先前返回的指针不匹配,或者空间已通过调用 free 或 realloc 释放,则行为未定义。

“未定义的行为”几乎总是程序崩溃。为了避免这种情况,可以安全地将指针重置为 NULL。free() 本身不能执行此操作,因为它仅传递一个指针,而不是指向指针的指针。您还可以编写一个更安全的 free() 版本,将指针设为 NULL:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}
Run Code Online (Sandbox Code Playgroud)

  • @Chris Lutz:废话。如果您编写的代码两次释放同一指针,则您的程序中存在逻辑错误。通过使其不崩溃来掩盖逻辑错误并不意味着程序是正确的:它仍然在做一些无意义的事情。编写双重释放是不合理的。 (4认同)
  • @Chris,不,最好的方法是代码结构。不要在代码库中随意放置 malloc 和 free,将相关的东西放在一起。分配资源(内存、文件等)的“模块”负责释放资源,并且必须提供一个函数来执行此操作,同时也照顾指针。对于任何特定资源,您都会有一个分配该资源的位置和一个释放该资源的位置,两者都紧邻。 (3认同)