释放后将变量设置为 NULL

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

void some_func ()
{
int *nPtr;


nPtr = malloc (100);


free (nPtr);
nPtr = NULL;


return;
}

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

如果在这种情况下没有意义,我将采取它与“质量团队”去除这个编码规则。请指教。

120896 次浏览

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

设置指针指向 free内存意味着任何试图通过指针访问该内存的尝试都会立即崩溃,而不会导致未定义行为。这让我们更容易判断问题出在哪里。

我可以看到你的论点: 因为 nPtr正好在 nPtr = NULL之后超出作用域,所以似乎没有理由将它设置为 NULL。但是,对于 struct成员或指针没有立即超出作用域的其他地方,这样做更有意义。这个指针是否会被不应该使用它的代码再次使用还不是很明显。

规则很可能是在没有区分这两种情况的情况下提出的,因为自动执行规则要困难得多,更不用说让开发人员遵循规则了。在每次空闲之后设置指向 NULL的指针并没有什么坏处,但是它有可能指出一些大问题。

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

对于局部变量,如果指针在被释放后不再被访问,那么这种风格对于成员数据和全局变量来说可能有点无意义。即使对于局部变量,如果函数在释放内存后继续运行,那么这也是一种很好的方法。

为了完成样式,您还应该在指向 NULL 的指针被赋予真正的指针值之前对它们进行初始化。

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

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

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

试试这样的方法:

#if DEBUG_VERSION
void myfree(void **ptr)
{
free(*ptr);
*ptr = NULL;
}
#else
#define myfree(p) do { void ** p_tmp = (p); free(*(p_tmp)); *(p_tmp) = NULL; } while (0)
#endif

DEBUG _ VERION 允许在调试代码中释放配置文件,但是两者在功能上是相同的。

编辑 : 添加做... 同时如下所建议,谢谢。

如果到达已经释放了 free () d 的指针,它可能会中断,也可能不会。这个内存可能会被重新分配到程序的另一部分然后你就会出现内存损坏,

如果您将指针设置为 NULL,那么如果您访问它,程序总是会因为 Segfault 而崩溃。没有更多,有时它工作“ ,没有更多,崩溃在不可预测的方式”。调试起来容易多了。

当你试图避免以下情况时,这条规则很有用:

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

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

在您的场景中,它没有太多的意义,但是如果函数变得更长,它可能很重要。

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

一般来说,我建议您在认为它是一个好主意时将它设置为 NULL,而在认为它不值得时不要烦恼。相反,应该专注于编写简短的函数和设计良好的类。

另外,使用指针的一个好方法是总是检查它是否是一个有效的指针。比如:


if(ptr)
ptr->CallSomeMethod();

在释放指针之后,将其显式标记为 NULL,这在 C/C + + 中是允许的。

这可能更像是一个初始化所有指向 NULL 的指针的参数,但是这样的东西可能是一个非常狡猾的 bug:

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 的迹象..。

C 中最常见的 bug 是 double free

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

结果非常糟糕,操作系统试图释放一些已经释放的内存,通常会导致 segfault 错误。所以最好的做法是将其设置为 NULL,这样您就可以进行测试并检查是否真的需要释放这个内存

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

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

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

将刚刚释放的指针设置为 NULL 不是强制性的,而是一种好的做法。这样,你就可以避免1)使用一个自由的指向2)释放它两次

将指针设置为 NULL 是为了再次保护所谓的 double-free ——当 free ()在同一个地址上被调用不止一次而不重新分配该地址上的块时,就会出现这种情况。

双自由导致未定义的行为-通常堆损坏或立即崩溃的程序。对 NULL 指针调用 free ()什么也不做,因此保证是安全的。

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

根据 ANSI C 标准:

void free(void *ptr);

自由函数导致空间 被 ptr 指向被释放, 也就是说,提供进一步的 如果 ptr 是空指针, 没有操作发生。否则,如果 参数与指针不匹配 早些时候由 calloc 归还, Malloc,或 realloc 函数,或者 空间已由 呼叫自由或现实,行为 是不确定的。

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

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

大多数响应都集中在防止双空闲,但是将指针设置为 NULL 还有另一个好处。一旦释放了一个指针,该内存就可以通过另一个 malloc 调用进行重新分配。如果你的原始指针还在,你可能会遇到一个 bug,你试图在 free 之后使用指针,并且破坏了其他一些变量,然后你的程序进入一个未知的状态,所有的坏事情都可能发生(如果你幸运的话会崩溃,如果你不幸的话会数据破坏)。如果在 free 之后将指针设置为 NULL,那么稍后通过该指针进行读/写操作的任何尝试都将导致 Segfault,这通常比随机内存损坏更可取。

出于这两个原因,最好在 free ()之后将指针设置为 NULL。不过也不是每次都有必要。例如,如果指针变量在 free ()之后立即超出作用域,就没有太多理由将其设置为 NULL。

有两个原因:

避免双释放时崩溃

愤怒重复的问题中编写。

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

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

结果很糟糕,操作系统尝试 释放一些已经释放的内存 通常是断层,所以好的 练习是设置为 NULL,所以你 可以进行测试和检查,如果你真的 需要释放这段记忆

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

还要注意的是,free(NULL) 不会做任何事,所以你不必 写 if 语句,我不是 虽然我是操作系统专家,但我们扯平了 现在大多数操作系统会崩溃两倍 自由。

这也是为什么所有 具有垃圾收集功能的语言 (Java,dotnet)是如此骄傲,没有 有这个问题,也没有 不得不离开开发商 整体内存管理。

避免使用已释放的指针

Martin 诉 Löwis 一案另一个答案中编写。

将未使用的指针设置为 NULL 是 防御性的,防御性的 迷途指针 指针在释放后被访问, 你可以随意读取或覆盖 如果一个空指针被访问, 在大多数情况下你会立即崩溃 系统,马上告诉你什么 错误是。

对于局部变量,它可能是 如果是的话就没什么意义了 “显然”指针不是 被释放之后就不能访问了,所以 这种款式更适合 成员数据和全局变量 对于局部变量,这可能是一个很好的 如果函数继续,则接近 在记忆被释放之后。

为了完成样式,您还应该 之前初始化指向 NULL 的指针 它们被赋予一个真正的指针 价值。

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

但是-如果指针不再被使用,我不会将变量设置为 null,但是通常更高级别的设计会给我一个理由将它设置为 null。例如,如果指针是一个类的一个成员,我已经删除了它指向的内容,那么如果你喜欢这个类,那么“约定”就是这个成员将指向某个在任何时候都有效的内容,因此它必须被设置为 null。区别很小,但我认为很重要。

在 c + + 中,在分配内存时,一定要考虑谁来 拥有这些数据(除非使用智能指针,但即使这样也需要考虑一些问题)。这个过程通常会导致指针成为某个类的成员,通常你希望一个类在任何时候都处于有效状态,最简单的方法是将成员变量设置为 NULL,以表明它现在没有指向任何东西。

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

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

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

据说,在 free之后设置一个指针指向 NULL,是为了防止当同一个指针值多次传递给 free时出现可怕的“双自由”问题。但实际上,在10个案例中有9个案例中,当持有相同指针值的 与众不同指针对象被用作 free的参数时,就会出现真正的“双自由”问题。不用说,在 free之后设置一个指针到 NULL完全不能防止这种情况下的问题。

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

最后,如果您的代码是专门设计来依赖于指针值是 NULL还是不是 NULL,那么在 free之后将指针值设置为 NULL是完全可以的。但是,作为一个一般的“良好实践”规则(就像“总是在 free之后把你的指针设置为 NULL”) ,它又一次是一个众所周知的和相当无用的假货,经常被一些纯粹的宗教,类似伏都教的原因所遵循。

既然您已经有了一个质量保证团队,那么让我补充一点关于质量保证的小问题。一些针对 C 语言的自动 QA 工具会将释放的指针的分配标记为“对 ptr的无用分配”。例如,Gimpl 软件公司的 PC-lint/FlexeLint 说 tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

有一些方法可以选择性地禁止消息,这样,如果您的团队决定这样做,您仍然可以同时满足这两个 QA 要求。

其思想是,如果您尝试在释放无效的指针之后解引用它,那么您希望严重失败(Segfault) ,而不是默默地、神秘地失败。

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

回到最初的问题: 在释放内容后直接将指针设置为 NULL 完全是浪费时间,只要代码满足所有要求,完全调试并且永远不会再次修改。另一方面,当有人不经思考地在 free ()下面添加了一个新的代码块,当原始模块的设计不正确时,以及在这种情况下——编译但不做我想要的错误时,使一个已释放的指针为空是非常有用的。

在任何系统中,都存在一个无法实现的目标,那就是让正确的事情变得更容易,以及不准确测量的不可减少的成本。在 C 语言中,我们得到了一套非常锋利、非常强大的工具,它们可以在熟练工人手中创造出许多东西,如果处理不当,会造成各种隐喻性的伤害。有些难以理解或正确使用。人们天生厌恶风险,所以会做一些非理性的事情,比如在调用 free 之前检查指针是否为 NULL 值... ..。

衡量的问题在于,无论何时你试图区分好与不好,情况越复杂,就越有可能得到一个模棱两可的衡量。如果目标是只保留好的实践,那么一些模棱两可的实践就会随着实际上不好的实践一起被抛弃。如果你的目标是消除不好的东西,那么模棱两可的东西可能会留在好的东西上。这两个目标,保持只有好或消除明显的坏,似乎是完全对立的,但通常有第三组,既不是一个或另一个,两者兼而有之。

在向质量部门陈述之前,请尝试查看错误数据库,看看无效的指针值是否经常导致必须写下来的问题。如果您希望真正改变现状,请确定生产代码中最常见的问题,并提出三种预防方法

最近我在寻找答案的过程中遇到了同样的问题,我得出了这样的结论:

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

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

所以一定要

free(ptr);
ptr = NULL;

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

int *ptr = NULL;

假设 Ptr指向 第一季,第1000集内存地址。 在使用 free(ptr)之后,通过再次声明为 无效来取消指针变量始终是明智的。 例如:

free(ptr);
ptr = NULL;

如果没有重新声明为 无效,指针变量仍然指向相同的地址(第一季,第1000集) ,这个指针变量称为 迷途指针。 如果您定义了另一个指针变量(比方说,)并动态地为新指针分配地址,那么有可能通过新的指针变量获取相同的地址(第一季,第1000集)。如果你使用相同的指针(Ptr)并更新指向相同指针(Ptr)的地址的值,那么程序最终会写一个值到 指向的地方(因为 P指向相同的地址(第一季,第1000集))。

例如:。

*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。