免费后将变量设置为NULL

时间:2009-06-22 05:35:17

标签: c coding-style malloc free heap-memory

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

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

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

如果在这种情况下没有任何意义,我将与“质量团队”讨论,删除此编码规则。请指教。

22 个答案:

答案 0 :(得分:273)

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

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

要完成样式,您还应该在指针指向真正的指针值之前将其初始化为NULL。

答案 1 :(得分:36)

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

据称,在NULL之后设置指向free的指针应该可以防止在将同一指针值多次传递给free时出现可怕的“双重自由”问题。实际上,在10个中的9个案例中,当具有相同指针值的不同指针对象用作free的参数时,会发生真正的“双重自由”问题。毋庸置疑,在NULL之后设置指向free的指针绝对没有什么可以防止在这种情况下出现问题。

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

最后,如果您的代码专门设计为依赖指针值为NULL而不是NULL,那么在{{1}之后将指针值设置为NULL是完全正确的。 }}。但作为一般的“良好做法”规则(如“总是在free之后将指针指向NULL”),它再一次是一个众所周知且无用的假货,通常会跟着一些纯粹的宗教,巫术般的原因。

答案 2 :(得分:31)

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

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

答案 3 :(得分:18)

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

尝试这样的事情:

#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

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

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

答案 4 :(得分:7)

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

我可以看到你的论点:由于nPtrnPtr = NULL之后就超出了范围,因此似乎没有理由将其设置为NULL。但是,对于struct成员或指针不会立即超出范围的其他位置,它更有意义。这个指针是否会被不应该使用它的代码再次使用,这一点并不明显。

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

答案 5 :(得分:7)

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

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

答案 6 :(得分:7)

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

free(foobar);
/* lot of code */
free(foobar);

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

if(foobar != null){
  free(foobar);
}

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

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

答案 7 :(得分:6)

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

答案 8 :(得分:4)

这(可能)实际上很重要。虽然你释放了内存,但程序的后期部分可能会分配一些新的空间。你的旧指针现在指向一个有效的内存块。然后有人可能会使用指针,导致程序状态无效。

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

答案 9 :(得分:4)

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

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

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

总是去

free(ptr);
ptr = NULL;

答案 10 :(得分:4)

来自ANSI C标准:

void free(void *ptr);
  

自由功能导致空间   ptr指出要解除分配,   也就是说,可用于进一步   分配。如果ptr是空指针,   没有动作发生。否则,如果   参数与指针不匹配   早先由calloc返回,   malloc,或realloc函数,或者如果   这个空间被一个人解除了分配   呼吁自由或realloc,行为   未定义。

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

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

答案 11 :(得分:4)

我发现这对我的帮助很小,因为根据我的经验,当人们访问释放的内存分配时,几乎总是因为他们在某处有另一个指向它的指针。然后它与另一个“避免无用的混乱”的个人编码标准冲突,所以我不这样做,因为我认为它很少有帮助并且使代码的可读性稍差。

但是 - 如果指针不应该再次使用,我不会将变量设置为null,但是更高级别的设计通常会让我有理由将其设置为null。例如,如果指针是类的成员并且我已经删除了它所指向的内容,那么如果您喜欢该类,那么“契约”是该成员将在任何时候指向有效的东西,因此它必须设置为null是因为。一个小的区别,但我认为一个重要的。

在c ++中,重要的是在分配一些内存时总是在考虑谁拥有这些数据(除非你使用的是智能指针,但即使这样,也需要一些思考)。并且这个过程往往会导致指针通常成为某个类的成员,并且通常您希望类始终处于有效状态,并且最简单的方法是将成员变量设置为NULL以指示它指向它现在什么都没有。

一种常见的模式是在构造函数中将所有成员指针设置为NULL,并在任何指向数据的指针上使用析构函数调用delete,这些数据指的是设计所指的类拥有。显然,在这种情况下,您必须在删除某些内容时将指针设置为NULL,以表明您之前没有任何数据。

总而言之,是的,我经常在删除内容之后将指针设置为NULL,但它是更大设计的一部分,并且考虑谁拥有数据而不是盲目遵循编码标准规则。我不会在你的例子中这样做,因为我认为这样做没有任何好处,并且它增加了“混乱”,这在我的经验中就像对这种事情一样对错误和错误代码负责。

答案 12 :(得分:3)

当您尝试避免以下情况时,此规则很有用:

1)你有一个非常长的函数,具有复杂的逻辑和内存管理,你不想在函数后面意外地重复使用指向已删除内存的指针。

2)指针是具有相当复杂行为的类的成员变量,并且您不希望在其他函数中意外地将指针重用于已删除的内存。

在你的场景中,它没有多大意义,但如果功能变得更长,那可能很重要。

您可能会认为将其设置为NULL实际上可能会在以后屏蔽逻辑错误,或者在您认为它有效的情况下,您仍会在NULL上崩溃,因此无关紧要。

一般情况下,当你认为这是一个好主意时,我建议你将它设置为NULL,而当你认为它不值得时,不要打扰它。而是专注于编写简短的函数和精心设计的类。

答案 13 :(得分:2)

设置一个指向NULL的指针是为了保护所谓的双重释放 - 一种情况,即对同一地址多次调用free()而不重新分配该地址的块。

双重自由导致未定义的行为 - 通常是堆损坏或立即崩溃程序。为空指针调用free()不会做任何事情,因此保证是安全的。

所以最好的做法是除非你现在确定指针在free()之后立即或很快离开作用域是将指针设置为NULL,这样即使再次调用free(),它现在也被调用为NULL指针并且避免了未定义的行为。

答案 14 :(得分:2)

原来的问题: 在释放内容后直接将指针设置为NULL是完全浪费时间,只要代码满足所有要求,完全调试并且永远不会再次修改。另一方面,当原始模块的设计不正确时,当有人在free()下面不加思索地添加新代码块时,防御性地对已释放的指针进行NULL操作非常有用,并且就此而言-compiles-but-not-do-what-I-want-bug。

在任何系统中,都有一个无法实现的目标,即最简单的方法,以及不准确的测量成本。在C中我们提供了一套非常锋利,非常强大的工具,这些工具可以在技术工人手中创造许多东西,并且在处理不当时会造成各种各样的隐喻伤害。有些难以理解或正确使用。自然风险厌恶的人会做一些非理性的事情,例如在使用它之前检查指针是否为NULL值...

测量问题是,无论何时尝试从不太好的方面划分好的东西,情况越复杂,就越有可能得到模糊的测量。如果目标是确保只保留良好的做法,那么一些含糊不清的实践就会被抛弃,实际上并不好。如果你的目标是消除不好的东西,那么歧义就可能与好的一致。两个目标,只保持良好或消除明显不好,似乎是截然相反的,但通常有第三组既不是一个也不是另一个,两者都是。

在向质量部门提出案例之前,请尝试查看错误数据库,看看无效的指针值是否经常导致必须记下的问题。如果您想真正发挥作用,请确定生产代码中最常见的问题,并提出三种方法来防止它

答案 15 :(得分:2)

这个想法是,如果你在释放它之后尝试取消引用不再有效的指针,你想要努力(段错误)而不是默默地和神秘地失败。

但是......小心点。如果取消引用NULL,并非所有系统都会导致段错误。在(至少某些版本)AIX上,*(int *)0 == 0,并且Solaris具有与此AIX“功能的可选兼容性。”

答案 16 :(得分:2)

有两个原因:

双重释放时避免崩溃

RageZ撰写duplicate question

  

c中最常见的错误是双重错误   自由。基本上你做的事情就像   该

free(foobar);
/* lot of code */
free(foobar);
     操作系统试试,结果非常糟糕   释放一些已经释放的记忆和   一般来说它是段错误的。好的   练习是设置为NULL,所以你   可以进行测试并检查你是否真的   需要释放这个记忆

if(foobar != NULL){
  free(foobar);
}
     

还要注意free(NULL)   不会做任何事情,所以你不必做   写下if语句。我不是   真的是一个操作系统大师,但我很平静   现在大多数操作系统都会崩溃   免费。

     

这也是所有人的主要原因   垃圾收集语言   (Java,dotnet)为此感到骄傲   有这个问题,也没有   不得不离开开发商   记忆管理作为一个整体。

避免使用已释放的指针

Martin v. Löwis撰写another answer

  

将未使用的指针设置为NULL是一个   防守风格,防范   悬挂指针错误。如果晃来晃去   指针在被释放后被访问,   你可以随意阅读或覆盖   记忆。如果访问空指针,   大多数人都会立即崩溃   系统,马上告诉你什么   错误是。

     

对于局部变量,它可能是a   如果是的话,有点无意义   “明显”指针不是   被释放后再访问,所以   这种风格更适合   成员数据和全局变量。甚至   对于局部变量,它可能是一个好的   如果功能继续,则接近   记忆释放后。

     

要完成风格,你也应该   之前将指针初始化为NULL   他们被分配了一个真正的指针   值。

答案 17 :(得分:2)

将刚刚释放为NULL的指针设置为非强制性,但这是一种很好的做法。通过这种方式,你可以避免1)使用一个释放的尖头2)释放它两个

答案 18 :(得分:2)

这可能更像是将所有指针初始化为NULL的参数,但是像这样的东西可能是一个非常偷偷摸摸的错误:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

p最终位于堆栈中与前nPtr相同的位置,因此它可能仍包含看似有效的指针。分配给*p可能会覆盖所有不相关的内容并导致丑陋的错误。特别是如果编译器在调试模式下将局部变量初始化为零,但一旦启用优化就不会。所以调试版本没有显示任何bug的迹象,而发布版本随机爆炸......

答案 19 :(得分:2)

要添加其他人所说的,指针使用的一个好方法是始终检查它是否是有效指针。类似的东西:


if(ptr)
   ptr->CallSomeMethod();

释放后将指针显式标记为NULL允许在C / C ++中使用这种用法。

答案 20 :(得分:1)

由于您有一个质量保证团队,请允许我添加一个关于质量保证的小问题。一些用于C的自动QA工具会将释放指针的赋值标记为“对ptr无用的赋值”。例如,Gimpel Software的PC-lint / FlexeLint说 tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

有一些方法可以有选择地抑制消息,因此如果您的团队决定,您仍然可以满足两个QA要求。

答案 21 :(得分:1)

始终建议使用 NULL 声明指针变量,例如,

int *ptr = NULL;

假设 ptr 指向 0x1000 内存地址。 使用free(ptr)后,始终建议通过再次声明 NULL 来使指针变量无效。 e.g:

free(ptr);
ptr = NULL;

如果没有重新声明为 NULL ,指针变量仍然指向同一个地址( 0x1000 ),这个指针变量称为悬空指针即可。 如果您定义另一个指针变量(比方说, q )并动态地为新指针分配地址,则有可能通过新指针获取相同的地址( 0x1000 )变量。如果是这种情况,你使用相同的指针( ptr )并更新同一指针指向的地址( ptr )的值,那么程序将最终编写一个值 q 指向的位置(因为 p q 指向同一地址( 0x1000 ) )。

e.g。

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.