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

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

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

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

有帮助吗?

解决方案

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

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

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

其他提示

free 之后设置指向 NULL 的指针是一种可疑的做法,通常被推广为“良好的编程”。对明显错误的前提作出裁决。这是属于“声音正确”的假真理之一。类别,但实际上绝对没有任何用处(有时会导致消极后果)。

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

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

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

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

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

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

尝试这样的事情:

#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,但两者在功能上是相同的。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

来自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;
}

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

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

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

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

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

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

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

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

总是去

free(ptr);
ptr = NULL;

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

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

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

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

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

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

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


if(ptr)
   ptr->CallSomeMethod();

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

这可能更像是将所有指针初始化为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的迹象,而发布版本随机爆炸......

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

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

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

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

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

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

原来的问题: 在释放内容后直接将指针设置为NULL是完全浪费时间,只要代码满足所有要求,完全调试并且永远不会再次修改。另一方面,当原始模块的设计不正确时,当有人不假思索地在free()下面添加新的代码块时,防御性地对已释放的指针进行NULL操作非常有用,并且就此而言-compiles-but-not-do-what-I-want-bug。 点击
在任何系统中,都有一个无法实现的目标,即最简单的方法,以及不准确的测量成本。在C中我们提供了一套非常锋利,非常强大的工具,这些工具可以在技术工人手中创造许多东西,并且在处理不当时会造成各种各样的隐喻伤害。有些难以理解或正确使用。而且,自然风险厌恶的人会做一些非理性的事情,例如在使用它之前检查指针是否为NULL值&#8230; 点击
测量问题是,无论何时尝试将好处与不太好的物品分开,情况越复杂,您获得模糊测量的可能性就越大。如果目标是确保只保留良好的做法,那么一些含糊不清的实践就会被抛弃,实际上并不好。如果你的目标是消除不好的东西,那么歧义就可能与好的一致。两个目标,只保持良好或消除明显不好,似乎是截然相反的,但通常有第三组既不是一个也不是另一个,两者都是。 点击
在向质量部门提出案例之前,请尝试查看错误数据库,看看无效的指针值是否经常导致必须记下的问题。如果您想真正发挥作用,请确定生产代码中最常见的问题,并提出三种方法来防止它

有两个原因:

双重释放时避免崩溃

RageZ 撰写的重复的问题

  

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

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

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

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

     

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

避免使用已释放的指针

Martin v.L&#246; wis 撰写另一个答案

  

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

     

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

     

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

由于您有一个质量保证团队,请允许我添加一个关于质量保证的小问题。一些用于C的自动QA工具会将释放指针的赋值标记为“无用分配给 ptr ”。例如,Gimpel Software的PC-lint / FlexeLint说 tst.c 8警告438:分配给变量'nPtr'(在第5行定义)的最后一个值未使用

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

始终建议使用 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.

长话短说:您不希望(错误地)访问您已释放的地址。因为,当您释放地址时,您允许将堆中的该地址分配给其他应用程序。

但是,如果没有将指针设置为NULL,并且错误地尝试取消引用指针,或者更改该地址的值;你还可以做到。但是你并不想做什么。

为什么我仍然可以访问已释放的内存位置?因为:您可能已释放内存,但指针变量仍然具有有关堆内存地址的信息。因此,作为防御策略,请将其设置为NULL。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top