代码之家  ›  专栏  ›  技术社区  ›  Alphaneo

释放后将变量设置为空

  •  139
  • Alphaneo  · 技术社区  · 17 年前

    在我的公司里有一个编码规则,在释放内存后,将变量重置为空。例如。。。

    void some_func () 
    {
        int *nPtr;
    
        nPtr = malloc (100);
    
        free (nPtr);
        nPtr = NULL;
    
        return;
    }
    

    我觉得,在类似上面显示的代码的情况下,设置为空没有任何意义。还是我错过了什么?

    如果在这种情况下没有意义,我将和“质量团队”一起删除这个编码规则。请教。

    23 回复  |  直到 7 年前
        1
  •  269
  •   Martin v. Löwis    17 年前

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

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

    要完成样式,还应在为指针赋值为真指针值之前将指针初始化为空。

        2
  •  36
  •   AnT stands with Russia    14 年前

    将指针设置为 NULL 之后 free 这是一个可疑的做法,通常作为一个“良好的编程”规则在一个明显错误的前提下被普及。这是属于“听起来正确”范畴的虚假事实之一,但实际上没有任何有用的东西(有时会导致负面后果)。

    据说,设置一个指针 无效的 之后 自由的 当相同的指针值传递给 自由的 不止一次。但实际上,在10个案例中,有9个案例中真正的“双重自由”问题发生在 不同的 具有相同指针值的指针对象用作 自由的 . 不用说,设置一个指针 无效的 之后 自由的 在这种情况下,根本无法阻止问题的发生。

    当然,当使用同一个指针对象作为参数 自由的 . 然而,在实际情况中,这种情况通常表明代码的一般逻辑结构存在问题,而不仅仅是偶然的“双重自由”。在这种情况下,处理该问题的一个正确方法是复查和重新思考代码的结构,以避免在将同一指针传递给 自由的 不止一次。在这种情况下,将指针设置为 无效的 考虑到问题“已解决”,只不过是试图把问题扫到地毯下而已。在一般情况下,它根本不起作用,因为代码结构的问题总是会找到另一种方法来表现自己。

    最后,如果您的代码专门设计为依赖 无效的 或不 无效的 ,将指针值设置为 无效的 之后 自由的 . 但是作为一般的“良好实践”规则(如“始终将指针设置为 无效的 之后 自由的 “)这又是一个众所周知的,相当无用的假货,后面往往是一些纯粹出于宗教和巫毒目的的假货。

        3
  •  31
  •   Mike McNertney    16 年前

    大多数响应都集中在防止双重释放上,但是将指针设置为空还有另一个好处。一旦释放一个指针,该内存就可以被另一个调用malloc重新分配。如果仍然有原始指针在身边,那么可能最终会出现一个bug,在释放并损坏其他变量后,您试图使用指针,然后您的程序进入未知状态,可能会发生各种错误(如果幸运,崩溃;如果不幸,数据损坏)。如果您在空闲后将指针设置为空,任何稍后通过该指针进行读/写的尝试都将导致segfault,这通常比随机内存损坏更可取。

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

        4
  •  17
  •   tcyrus    7 年前

    这被认为是避免重写内存的好方法。在上面的函数中,这是不必要的,但通常当它完成时,它会发现应用程序错误。

    改为尝试类似的操作:

    #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_版本允许您在调试代码中释放概要文件,但两者在功能上是相同的。

    编辑 增加…如下文所述,谢谢。

        5
  •  7
  •   Tadeusz A. Kadłubowski    17 年前

    如果您到达的指针是free()d,它可能会断开或不断开。内存可能会重新分配到程序的另一部分,然后内存会损坏,

    如果您将指针设置为空,那么如果您访问它,程序总是崩溃,并出现一个segfault。不再,有时它工作,不再,以不可预知的方式崩溃。这样更容易调试。

        6
  •  7
  •   Jared Oberhaus    17 年前

    将指针设置为 free ‘d内存意味着任何通过指针访问内存的尝试都将立即崩溃,而不会导致未定义的行为。这使得判断出哪里出了问题变得容易得多。

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

    很可能规则是在没有区分这两种情况的情况下声明的,因为自动执行规则要困难得多,更不用说开发人员遵循它了。把指针指向 无效的 每次自由之后,都有可能指出大问题。

        7
  •  7
  •   RageZ    16 年前

    C中最常见的bug是双自由。基本上你是这样做的

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

    结果很糟糕,操作系统试图释放一些已经释放的内存,通常是segfault。所以好的做法是 NULL ,以便您可以进行测试并检查是否确实需要释放此内存

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

    还要注意的是 free(NULL) 不会做任何事情,所以您不必编写if语句。我不是一个真正的操作系统专家,但我很漂亮,即使现在大多数操作系统会崩溃在双重免费。

    这也是为什么所有的垃圾收集语言(Java,DOTNET)都很自豪地没有这个问题,也不必让开发人员把内存管理作为一个整体。

        8
  •  6
  •   Mitch Wheat    17 年前

    其背后的想法是,停止意外重用释放的指针。

        9
  •  4
  •   Steven Canfield    17 年前

    这实际上很重要。虽然您释放了内存,但是程序的后面部分可以分配一些新的东西,这些东西会发生在空间中。您的旧指针现在将指向一个有效的内存块。然后有可能有人会使用指针,从而导致无效的程序状态。

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

        10
  •  4
  •   Vijay Mathew Chor-ming Lung    16 年前

    根据ANSI C标准:

    void free(void *ptr);
    

    自由函数导致空间 由待解除分配的ptr指出, 也就是说,进一步提供 分配。如果ptr是空指针, 未发生任何操作。否则,如果 参数与指针不匹配 之前由Calloc返回, malloc或realloc函数,或者如果 该空间已由 呼吁自由或重新分配行为 是未定义的。

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

    void safe_free(void** ptr)
    {
      free(*ptr);
      *ptr = NULL;
    }
    
        11
  •  4
  •   jcoder    16 年前

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

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

    在C++中,重要的是始终在思考谁 拥有 当您分配一些内存时,这个数据(除非您使用智能指针,但甚至需要一些思考)。这个过程往往会导致指针通常是某个类的成员,通常您希望某个类始终处于有效状态,最简单的方法是将成员变量设置为空以指示它现在不指向任何对象。

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

    总而言之,是的,我经常在删除某个内容后将指针设置为空,但这是更大设计的一部分,并考虑谁拥有数据,而不是盲目地遵循编码标准规则。在您的示例中,我不会这样做,因为我认为这样做没有好处,而且它添加了“杂乱”,在我的经验中,这和此类事情一样对错误和坏代码负责。

        12
  •  4
  •   Barmar    13 年前

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

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

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

    所以总是去

    free(ptr);
    ptr = NULL;
    
        13
  •  3
  •   i_am_jorf    17 年前

    当您试图避免以下情况时,此规则非常有用:

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

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

    在您的场景中,这并没有什么意义,但是如果函数变长了,这可能很重要。

    您可能会争辩说,将其设置为空可能会在以后掩盖逻辑错误,或者在假定它有效的情况下,您仍然会崩溃于空,所以这并不重要。

    一般来说,如果你认为这是个好主意,我建议你把它设为空,如果你认为它不值得,我建议你不要麻烦。专注于编写简短的函数和设计良好的类。

        14
  •  2
  •   Aamir    17 年前

    另外,一个好的指针使用方法是始终检查它是否是有效的指针。类似:

    
    if(ptr)
       ptr->CallSomeMethod();
    
    

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

        15
  •  2
  •   sth    17 年前

    这可能更像是一个初始化所有指向空值的指针的参数,但类似这样的错误可能是非常隐蔽的:

    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 可能会覆盖所有不相关的东西,导致丑陋的虫子。尤其是当编译器在调试模式下用零初始化局部变量,但在启用优化后却没有初始化。所以当发布版本随机爆炸时,调试版本不会显示任何错误的迹象…

        16
  •  2
  •   pierrotlefou    16 年前

    将刚刚释放的指针设置为空不是强制的,而是一个好的实践。这样,您就可以避免1)使用自由尖2)自由它拖缆

        17
  •  2
  •   sharptooth    16 年前

    设置指向空值的指针是为了保护所谓的双空闲-当同一地址多次调用free()而不在该地址重新分配块时的情况。

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

    因此,除非您现在确定指针立即离开作用域或在free()之后很快离开作用域,否则最好的做法是将该指针设置为空,这样即使再次调用free(),也会调用该指针,从而避免未定义的行为。

        18
  •  2
  •   Jaap Weel    14 年前

    其思想是,如果在释放不再有效的指针后尝试取消对它的引用,那么您希望硬失败(segfault),而不是默默无闻地、神秘地失败。

    但是…小心。如果取消对空值的引用,并非所有系统都会导致segfault。在(至少某些版本的)AIX上,*(int*)0==0,并且Solaris与此AIX“特性”具有可选的兼容性。

        19
  •  2
  •   Bill IV    14 年前

    对于原始问题: 在释放内容后直接将指针设置为空是完全浪费时间的,前提是代码满足所有要求,已完全调试并且不会再修改。另一方面,如果有人在free()下面无意中添加了一个新的代码块,当原始模块的设计不正确时,以及在编译但不执行我想要的错误的情况下,防御性地取消已释放的指针可能非常有用。

    在任何一个系统中,都有一个不可实现的目标,那就是使它最容易做正确的事情,以及不准确测量的不可减少的成本。在C语言中,我们提供了一套非常锋利、非常强大的工具,这些工具可以在熟练工人手中创造许多东西,并且在处理不当时会造成各种隐喻性的伤害。有些很难理解或正确使用。人们天生不喜欢冒险,做一些不合理的事情,比如在用空值调用free之前检查一个指针。

    测量问题是,每当你试图把好的和不好的分开时,情况越复杂,你就越有可能得到不明确的测量结果。如果目标是只保留好的实践,那么一些模棱两可的实践就会被实际不好的实践所抛弃。如果你的目标是消除不好的东西,那么模棱两可的东西可能会留在好的东西上。这两个目标,只保留好的或消除明显的坏的,似乎是截然相反的,但通常有第三组既不是一个也不是另一个,有些两者都不是。

    在向质量部门提出案例之前,请尝试查看bug数据库,以了解无效指针值导致必须写下问题的频率(如果有的话)。如果你想要真正的改变,找出你的生产代码中最常见的问题,并提出三种方法来防止它的发生。

        20
  •  2
  •   Georg Schölly    7 年前

    有两个原因:

    避免在双重释放时崩溃

    由书面 RageZ 在一个 duplicate question .

    C中最常见的错误是double 免费。基本上你会做一些 那个

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

    结果很糟糕,操作系统尝试 释放一些已经释放的内存 一般为分段故障。所以好 练习是要设置为 NULL 所以你 可以做测试并检查你是否真的 需要释放内存

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

    还要注意的是 free(NULL) 什么都不会做所以你不必 编写if语句。我不是 真的是一个操作系统专家,但我很平衡 现在大多数操作系统都会以双倍的速度崩溃 免费。

    这也是为什么 带有垃圾收集的语言 (Java,dotnet)非常自豪 有这个问题也没有 不得不离开开发商 整体内存管理。

    避免使用已释放的指针

    由书面 Martin v. Löwis 在一个 another answer .

    将未使用的指针设置为空是 防御风格,防御 悬空指针错误。如果悬挂 释放指针后访问指针, 您可以随机读取或覆盖 记忆。如果访问空指针, 大多数情况下你会立即崩溃 系统,马上告诉你 误差是。

    对于局部变量,它可能是 有点毫无意义 很明显指针不是 释放后再访问,所以 这种样式更适合 成员数据和全局变量。偶数 对于局部变量,它可能是一个很好的 如果函数继续,则接近 释放内存后。

    要完成样式,还应该 在前面初始化指向空值的指针 他们得到一个真正的指针 价值。

        21
  •  1
  •   Jens    15 年前

    既然你们有一个质量保证团队,让我补充一点关于QA的内容。一些针对C的自动化QA工具将释放指针的分配标记为“无用的分配给 ptr “。例如,来自Gimpel软件的pc lint/flexelint说 tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

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

        22
  •  1
  •   pkthapa    9 年前

    始终建议使用 无效的 比如,

    int *ptr = NULL;
    

    让我们说, PTR 指向 0x1000 内存地址。 使用后 free(ptr) ,始终建议通过再次声明 无效的 . 例如。:

    free(ptr);
    ptr = NULL;
    

    如果没有重新声明 无效的 ,指针变量仍继续指向同一地址( 0x1000 ,此指针变量称为 悬空指针 . 如果定义另一个指针变量(比如, Q )动态分配地址给新指针,就有可能获得相同的地址( 0x1000 )通过新的指针变量。如果在这种情况下,使用相同的指针( PTR )更新同一指针指向的地址值( PTR ,然后程序将写入一个值到 Q 指向(因为 Q 指向同一地址( 0x1000 )

    例如

    *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.
    
        23
  •  1
  •   Ehsan    9 年前

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

    但是,如果不将指针设置为空,并且错误地尝试取消对指针的引用,或者更改该地址的值;您仍然可以这样做。但这不是你逻辑上想做的。

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