提问人:Alphaneo 提问时间:6/22/2009 最后编辑:SimsonAlphaneo 更新时间:7/24/2020 访问量:130641
释放后将变量设置为 NULL
Setting variable to NULL after free
问:
在我的公司中,有一条编码规则,即在释放任何内存后,将变量重置为 .例如。。。NULL
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
我觉得,在像上面显示的代码这样的情况下,设置为没有任何意义。还是我错过了什么?NULL
如果在这种情况下没有意义,我将与“质量团队”一起删除此编码规则。请指教。
答:
这背后的想法是阻止意外重用释放的指针。
将指针设置为“d 内存”意味着任何通过指针访问该内存的尝试都将立即崩溃,而不是导致未定义的行为。它使确定哪里出了问题变得更加容易。free
我可以看到你的论点:既然紧接着就超出了范围,似乎没有理由将其设置为 。但是,对于成员或指针不会立即超出范围的其他位置,它更有意义。目前尚不清楚该指针是否会被不应使用它的代码再次使用。nPtr
nPtr = NULL
NULL
struct
该规则很可能没有区分这两种情况,因为自动执行该规则要困难得多,更不用说开发人员遵循它了。在每次空闲后设置指针并没有什么坏处,但它有可能指出大问题。NULL
将未使用的指针设置为 NULL 是一种防御方式,可防止悬空指针错误。如果在释放悬空指针后访问该指针,则可以读取或覆盖随机内存。如果访问了空指针,则大多数系统会立即崩溃,并立即告诉您错误是什么。
对于局部变量,如果“明显”的指针在被释放后不再被访问,这可能有点毫无意义,因此这种样式更适合成员数据和全局变量。即使对于局部变量,如果函数在释放内存后继续运行,也可能是一个好方法。
若要完成样式,还应先初始化指向 NULL 的指针,然后再为其分配真正的指针值。
评论
int *nPtr=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_VERSION允许您在调试代码中分析空闲,但两者在功能上是相同的。
编辑:添加了做...如下所述,谢谢。
评论
do { } while(0)
if(x) myfree(x); else dostuff();
do {X} while (0)
如果到达已 free()d 的指针,它可能会中断或不中断。该内存可能会被重新分配给程序的另一部分,然后内存损坏,
如果将指针设置为 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
C 语言中最常见的 bug 是 double free。基本上你做这样的事情
free(foobar);
/* lot of code */
free(foobar);
结果非常糟糕,操作系统试图释放一些已经释放的内存,通常它会出现段错误。所以好的做法是设置为 ,这样你就可以进行测试并检查你是否真的需要释放这个内存NULL
if(foobar != null){
free(foobar);
}
另外要注意的是,它不会做任何事情,所以你不必写 if 语句。我不是真正的操作系统大师,但即使现在大多数操作系统都会在双重释放时崩溃。free(NULL)
这也是为什么所有具有垃圾回收功能的语言(Java、dotnet)都为没有这个问题而感到自豪,也不必将内存管理作为一个整体留给开发人员的主要原因。
评论
p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
free(void *ptr)
free(void **ptr)
NULL
free
sizeof
将刚刚释放的指针设置为 NULL 不是强制性的,而是一种很好的做法。这样,您可以避免 1) 使用释放的尖头 2) 释放它 towice
设置 指向 NULL 的指针是为了保护所谓的 double-free - 这种情况是,对于同一地址多次调用 free(),而不在该地址重新分配块。
双重释放会导致未定义的行为 - 通常是堆损坏或立即使程序崩溃。为 NULL 指针调用 free() 不会执行任何操作,因此可以保证安全。
因此,除非您现在确定指针在 free() 之后立即或很快离开范围,否则最佳做法是将该指针设置为 NULL,这样即使再次调用 free(),现在也会调用 NULL 指针并规避未定义的行为。
根据 ANSI C 标准:
void free(void *ptr);
free 函数导致空格 PTR 指向要解除分配的, 也就是说,可用于进一步 分配。如果 ptr 为 null 指针, 不执行任何操作。否则,如果 参数与指针不匹配 早些时候由 calloc 返回, malloc 或 realloc 函数,或者如果 空间已由 调用 free 或 realloc ,行为 未定义。
“未定义的行为”几乎总是程序崩溃。为了避免这种情况,将指针重置为 NULL 是安全的。free() 本身无法做到这一点,因为它只传递一个指针,而不是指向指针的指针。您还可以编写一个更安全的 free() 版本,将指针 NULL 化:
void safe_free(void** ptr)
{
free(*ptr);
*ptr = NULL;
}
评论
NULL
大多数响应都集中在防止双重释放上,但将指针设置为 NULL 还有另一个好处。释放指针后,该内存可以通过对 malloc 的另一次调用重新分配。如果你仍然有原始指针,你最终可能会遇到一个错误,你试图在释放后使用指针并损坏其他一些变量,然后你的程序进入未知状态,各种坏事都可能发生(如果你幸运的话会崩溃,如果你不走运,就会发生数据损坏)。如果在释放后将指针设置为 NULL,则以后通过该指针进行读/写的任何尝试都会导致段错误,这通常比随机内存损坏更可取。
出于这两个原因,最好在 free() 之后将指针设置为 NULL。不过,这并不总是必要的。例如,如果指针变量在 free() 之后立即超出范围,则没有太多理由将其设置为 NULL。
评论
free
(void*)0xdeadbeef
有两个原因:
避免双重释放时崩溃
c 中最常见的 bug 是 double 自由。基本上你做这样的事情 那
free(foobar); /* lot of code */ free(foobar);
结果很糟糕,操作系统尝试 释放一些已经释放的内存和 通常,它是段错误。所以好的 练习是要设置到,所以你 可以进行测试并检查您是否真的 需要释放此内存
NULL
if(foobar != NULL){ free(foobar); }
另外要注意的是,它不会做任何事情,所以你不必这样做 编写 if 语句。我不是 真的是一个操作系统大师,但我甚至很漂亮 现在大多数操作系统都会在双倍时崩溃 自由。
free(NULL)
这也是所有 使用垃圾回收的语言 (Java,dotnet)如此自豪 有这个问题,也没有 不得不留给开发人员 内存管理作为一个整体。
避免使用已释放的指针
由 Martin V. Löwis 在另一个答案中撰写。
将未使用的指针设置为 NULL 是 防守风格,防止 悬空指针错误。如果晃来晃去 指针在释放后被访问, 您可以随机读取或覆盖 记忆。如果访问了 null 指针, 大多数情况下,您都会立即崩溃 系统,马上告诉你什么 错误是。
对于局部变量,它可能是 如果是的话,有点毫无意义 “明显”指针不是 被释放后再访问,所以 这种风格更适合 成员数据和全局变量。甚至 对于局部变量,它可能是一个很好的 如果功能继续,则接近 释放内存后。
要完成样式,您还应该 在之前初始化指向 NULL 的指针 他们被分配了一个真正的指针 价值。
我发现这没什么帮助,因为根据我的经验,当人们访问释放的内存分配时,几乎总是因为他们在某处有另一个指向它的指针。然后它与另一个个人编码标准冲突,即“避免无用的混乱”,所以我不这样做,因为我认为它很少有帮助,并且使代码的可读性略低。
但是 - 如果指针不应该再次使用,我不会将变量设置为 null,但通常更高级别的设计会让我有理由将其设置为 null。例如,如果指针是某个类的成员,并且我已经删除了它所指向的内容,那么如果您喜欢该类,则该类的“契约”是该成员将随时指向有效内容,因此必须将其设置为 null。这是一个很小的区别,但我认为是一个重要的区别。
在 c++ 中,在分配一些内存时,始终考虑谁拥有这些数据是很重要的(除非你使用的是智能指针,但即使如此,也需要一些思考)。这个过程往往会导致指针通常是某个类的成员,并且通常你希望一个类始终处于有效状态,最简单的方法是将成员变量设置为 NULL,以指示它现在不指向任何内容。
一种常见的模式是在构造函数中将所有成员指针设置为 NULL,并让析构函数对指向设计声明该类拥有的数据的任何指针调用 delete。显然,在这种情况下,您必须在删除某些内容时将指针设置为 NULL,以表明您之前不拥有任何数据。
总而言之,是的,我经常在删除某些内容后将指针设置为 NULL,但这是作为更大的设计和关于谁拥有数据的想法的一部分,而不是由于盲目遵循编码标准规则。在您的示例中,我不会这样做,因为我认为这样做没有任何好处,并且它增加了“混乱”,根据我的经验,这与此类事情一样会导致错误和错误代码。
设置指向 after 的指针是一种可疑的做法,它经常被推广为“好的编程”规则,其前提显然是错误的。它是属于“听起来正确”类别的虚假真相之一,但实际上绝对没有任何用处(有时会导致负面后果)。NULL
free
据称,设置指向 after 的指针应该可以防止在多次传递相同的指针值时出现可怕的“双重释放”问题。但实际上,在 10 种情况下,有 9 种情况是,当使用具有相同指针值的不同指针对象作为 的参数时,就会出现真正的“双重释放”问题。毋庸置疑,在这种情况下,设置指向 after 的指针绝对无法防止出现问题。NULL
free
free
free
NULL
free
当然,当使用相同的指针对象作为参数时,可能会遇到“双重释放”问题。然而,在现实中,这样的情况通常表明代码的一般逻辑结构存在问题,而不仅仅是偶然的“双重释放”。在这种情况下,处理问题的正确方法是审查和重新考虑代码的结构,以避免出现多次传递同一指针的情况。在这种情况下,将指针设置为“已解决”的问题,只不过是试图将问题扫地出门。在一般情况下,它根本行不通,因为代码结构的问题总是会找到另一种方式来表现自己。free
free
NULL
最后,如果您的代码专门设计为依赖于指针值 being or not ,则将指针值设置为 after 是完全可以的。但作为一般的“良好做法”规则(如“始终将指针设置为”),它再次成为众所周知且毫无用处的假货,经常被一些人出于纯粹的宗教、巫毒教般的原因而遵循。NULL
NULL
NULL
free
NULL
free
评论
foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;
bar
NULL
free
由于您有一个质量保证团队,让我补充一点关于 QA 的小观点。一些针对 C 的自动化 QA 工具会将对释放指针的赋值标记为“无用的赋值”。例如,Gimpel Software 的 PC-lint/FlexeLint 说ptr
tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used
有一些方法可以有选择地禁止显示消息,因此,如果您的团队决定这样做,您仍然可以同时满足这两个 QA 要求。
这个想法是,如果你试图在释放不再有效的指针后取消引用它,你希望失败(段错误),而不是默默地和神秘地失败。
但。。。小心。如果取消引用 NULL,并非所有系统都会导致段错误。在(至少某些版本的)AIX 上,*(int *)0 == 0,并且 Solaris 具有与此 AIX“功能”的可选兼容性。
对于最初的问题:
在释放内容后直接将指针设置为 NULL 完全是浪费时间,前提是代码满足所有要求,已完全调试并且永远不会再次修改。另一方面,当有人不经意地在 free() 下添加一个新代码块时,当原始模块的设计不正确时,以及在它编译但不做我想要的错误的情况下,防御性地 NULLing 一个已释放的指针可能非常有用。
在任何系统中,都有一个无法实现的目标,即让事情变得最简单,以及不准确测量的不可减少的成本。在C语言中,我们得到了一套非常锋利、非常坚固的工具,它们可以在熟练工人的手中创造很多东西,如果处理不当,会造成各种隐喻性的伤害。有些很难理解或正确使用。而人们,天生厌恶风险,会做一些非理性的事情,比如在调用 free 之前检查指针是否有 NULL 值......
测量问题是,每当你试图将好与差分开时,情况越复杂,你就越有可能得到一个模棱两可的测量。如果目标是只保持良好的做法,那么一些模棱两可的做法就会被扔掉,实际上并不好。如果你的目标是消除不好的东西,那么模棱两可的东西可能会留在好的地方。这两个目标,只保持好或消除明显的坏,似乎是截然相反的,但通常有第三组既不是其中之一,也不是另一个,两者兼而有之。
在向质量部门提出案例之前,请尝试查看错误数据库,以了解无效指针值导致必须写下的问题的频率(如果有的话)。如果你想做出真正的改变,请确定生产代码中最常见的问题,并提出三种方法来防止它
评论
最近,我在寻找答案后遇到了同样的问题。我得出了这个结论:
这是最佳实践,必须遵循此原则才能使其在所有(嵌入式)系统上具有可移植性。
free()
是一个库函数,它随着平台的变化而变化,因此不应期望在将指针传递给此函数并释放内存后,此指针将设置为 NULL。对于为平台实现的某些库,情况可能并非如此。
所以总是去
free(ptr);
ptr = NULL;
始终建议声明一个带有 NULL 的指针变量,例如,
int *ptr = NULL;
假设 ptr 指向0x1000内存地址。
使用 后,始终建议通过再次声明为 NULL 来使指针变量无效。
例如:free(ptr)
free(ptr);
ptr = NULL;
如果未重新声明为 NULL,则指针变量仍会继续指向同一地址 (0x1000),此指针变量称为悬空指针。 如果定义另一个指针变量(比如 q)并动态地将地址分配给新指针,则有可能通过新的指针变量获取相同的地址 (0x1000)。如果您使用相同的指针 (ptr) 并更新同一指针 (ptr) 指向的地址处的值,则程序最终会将值写入 q 指向的位置(因为 p 和 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.
长话短说:您不想意外(错误地)访问已释放的地址。因为,当您释放地址时,您允许将堆中的该地址分配给其他应用程序。
但是,如果未将指针设置为 NULL,并且错误地尝试取消引用指针,或更改该地址的值;你仍然可以做到。但不是你逻辑上想要做的事情。
为什么我仍然可以访问已释放的内存位置?因为:您可能已经释放了内存,但指针变量仍然包含有关堆内存地址的信息。因此,作为防御策略,请将其设置为 NULL。
上一个:什么是内存堆?
评论
ptr == NULL
ptr != NULL