在我的公司有一条编码规则,即在释放任何内存后,将变量重置为NULL
. 例如 ...
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
我觉得,在上面显示的代码这样的情况下,设置为NULL
没有任何意义。还是我错过了什么?
如果在这种情况下没有任何意义,我将与“质量团队”一起将这个编码规则删除。请指教。
在我的公司有一条编码规则,即在释放任何内存后,将变量重置为NULL
. 例如 ...
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
我觉得,在上面显示的代码这样的情况下,设置为NULL
没有任何意义。还是我错过了什么?
如果在这种情况下没有任何意义,我将与“质量团队”一起将这个编码规则删除。请指教。
将未使用的指针设置为 NULL 是一种防御方式,可防止出现悬空指针错误。如果悬空指针在被释放后被访问,您可能会读取或覆盖随机内存。如果访问空指针,大多数系统会立即崩溃,并立即告诉您错误是什么。
对于局部变量,如果指针在被释放后不再被访问是“明显的”,这可能有点无意义,所以这种风格更适合成员数据和全局变量。即使对于局部变量,如果函数在内存释放后继续执行,这可能是一个好方法。
要完成样式,您还应该在为指针分配真正的指针值之前将指针初始化为 NULL。
大多数响应都集中在防止双重释放,但是将指针设置为 NULL 有另一个好处。一旦你释放了一个指针,这个内存就可以被另一个 malloc 调用重新分配。如果你周围还有原来的指针,你可能会遇到一个错误,你在释放和破坏其他变量后尝试使用指针,然后你的程序进入一个未知状态并且可能发生各种不好的事情(如果你'很幸运,如果你不走运,数据损坏)。如果您在释放后将指针设置为 NULL,那么稍后通过该指针读取/写入的任何尝试都会导致段错误,这通常比随机内存损坏更可取。
出于这两个原因,最好在 free() 之后将指针设置为 NULL。不过,这并不总是必要的。例如,如果指针变量在 free() 之后立即超出范围,则没有太多理由将其设置为 NULL。
设置指向NULL
after的指针free
是一种可疑的做法,通常在明显错误的前提下被推广为“良好的编程”规则。这是属于“听起来正确”类别的虚假事实之一,但实际上绝对没有任何用处(有时会导致负面后果)。
据称,设置指向NULL
after的指针free
应该可以防止在free
多次传递相同的指针值时出现可怕的“双重释放”问题。但实际上,在 10 次中有 9 次的情况下,当持有相同指针值的不同指针对象用作free
. 不用说,在这种情况下,设置一个指向NULL
after的指针free
绝对不能防止问题发生。
当然,当使用相同的指针对象作为free
. 然而,在现实中,这样的情况通常表明代码的一般逻辑结构存在问题,而不仅仅是偶然的“双重释放”。在这种情况下处理问题的正确方法是审查并重新考虑代码的结构,以避免同一指针被free
多次传递的情况。在这种情况下,将指针设置为NULL
“已解决”并考虑问题只不过是试图将问题扫到地毯下。它在一般情况下根本行不通,因为代码结构的问题总会找到另一种表现形式。
最后,如果您的代码专门设计为依赖于指针值是否NULL
存在,则将指针值设置为afterNULL
是完全可以的。但作为一般的“良好实践”规则(如“始终将指针指向之后”),它再次成为众所周知且相当无用的假货,通常出于纯粹的宗教、类似伏都教的原因而遵循。NULL
free
NULL
free
这被认为是避免覆盖内存的良好做法。在上面的函数中,它是不必要的,但经常在执行时会发现应用程序错误。
尝试这样的事情:
#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 ... 虽然如下所示,谢谢。
将指针设置为free
'd 内存意味着任何通过指针访问该内存的尝试都将立即崩溃,而不是导致未定义的行为。它可以更容易地确定哪里出了问题。
我可以看到您的论点:由于nPtr
在 之后立即超出范围nPtr = NULL
,因此似乎没有理由将其设置为NULL
. 但是,对于struct
成员或指针不会立即超出范围的其他地方,它更有意义。不应该立即使用该指针的代码是否会再次使用它并不是很明显。
很可能在没有区分这两种情况的情况下声明了规则,因为自动执行规则要困难得多,更不用说开发人员遵循它了。在每次释放后设置指针并没有什么坏处NULL
,但它有可能指出大问题。
如果您到达已经 free()d 的指针,它可能会中断或不中断。该内存可能会重新分配给程序的另一部分,然后您会遇到内存损坏,
如果您将指针设置为 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);
free 函数会导致 ptr 指向的空间被释放,也就是说,可用于进一步分配。如果 ptr 是空指针,则不会发生任何操作。否则,如果参数与 calloc 、 malloc 或 realloc 函数先前返回的指针不匹配,或者如果空间已通过调用 free 或 realloc 被释放,则行为未定义。
“未定义的行为”几乎总是程序崩溃。为了避免这种情况,将指针重置为 NULL 是安全的。free() 本身不能这样做,因为它只传递了一个指针,而不是指向指针的指针。您还可以编写一个更安全的 free() 版本,将指针设为 NULL:
void safe_free(void** ptr)
{
free(*ptr);
*ptr = NULL;
}
最近我在寻找答案后遇到了同样的问题。我得出了这个结论:
这是最佳实践,必须遵循这一点才能使其在所有(嵌入式)系统上都可移植。
free()
是一个库函数,它随着平台的变化而变化,所以你不应该期望在将指针传递给这个函数并释放内存之后,这个指针会被设置为 NULL。对于为平台实现的某些库,情况可能并非如此。
所以总是去
free(ptr);
ptr = NULL;
我发现这没什么帮助,因为根据我的经验,当人们访问已释放的内存分配时,几乎总是因为他们在某处有另一个指向它的指针。然后它与另一个个人编码标准“避免无用的混乱”相冲突,所以我不这样做,因为我认为它很少有帮助并且使代码的可读性稍微降低。
但是 - 如果不应该再次使用指针,我不会将变量设置为 null,但通常更高级别的设计让我有理由将其设置为 null。例如,如果指针是一个类的成员并且我已经删除了它指向的内容,那么如果你喜欢这个类,那么“合同”就是该成员将指向任何时候有效的东西,所以它必须设置为 null是因为。一个小的区别,但我认为一个重要的区别。
在 C++ 中,重要的是在分配一些内存时始终考虑谁拥有这些数据(除非您使用智能指针,但即使这样也需要一些想法)。而且这个过程往往会导致指针通常是某个类的成员,并且通常您希望一个类始终处于有效状态,最简单的方法是将成员变量设置为 NULL 以指示它指向现在一无所有。
一种常见的模式是在构造函数中将所有成员指针设置为 NULL,并让析构函数对指向您的设计所说的类拥有的数据的任何指针调用 delete 。显然,在这种情况下,您必须在删除某些内容时将指针设置为 NULL,以表明您之前不拥有任何数据。
总而言之,是的,我经常在删除某些内容后将指针设置为 NULL,但这是更大的设计和思考谁拥有数据的一部分,而不是由于盲目地遵循编码标准规则。在您的示例中,我不会这样做,因为我认为这样做没有任何好处,而且它会增加“混乱”,根据我的经验,这与此类事情一样会导致错误和错误代码。
当您试图避免以下情况时,此规则很有用:
1)您有一个非常长的函数,具有复杂的逻辑和内存管理,并且您不希望稍后在函数中意外重用指向已删除内存的指针。
2)指针是具有相当复杂行为的类的成员变量,您不想在其他函数中意外重用指向已删除内存的指针。
在您的场景中,它没有多大意义,但如果函数变得更长,它可能很重要。
您可能会争辩说,将其设置为 NULL 实际上可能会在以后掩盖逻辑错误,或者在您认为它有效的情况下,您仍然会在 NULL 上崩溃,所以没关系。
一般来说,我建议您在认为这是个好主意时将其设置为 NULL,而在您认为不值得时不要打扰。而是专注于编写简短的函数和设计良好的类。
这可能更像是初始化所有指向 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
可能会覆盖各种不相关的东西并导致丑陋的错误。特别是如果编译器在调试模式下将局部变量初始化为零,但一旦打开优化就不会。因此,调试版本不会显示任何错误迹象,而发布版本会随机爆炸......
将刚刚释放的指针设置为 NULL 不是强制性的,而是一个很好的做法。这样,您可以避免 1) 使用已释放的指针 2) 将其释放两次
有两个原因:
c 中最常见的错误是双重释放。基本上你会做这样的事情
free(foobar); /* lot of code */ free(foobar);
结果很糟糕,操作系统试图释放一些已经释放的内存,通常是段错误。所以最好的做法是设置为
NULL
,这样你就可以进行测试并检查你是否真的需要释放这个内存if(foobar != NULL){ free(foobar); }
还要注意的是,它
free(NULL)
不会做任何事情,因此您不必编写 if 语句。我不是真正的操作系统专家,但即使现在大多数操作系统都会在双重免费时崩溃,我仍然很漂亮。这也是为什么所有具有垃圾收集的语言(Java、dotnet)都为没有这个问题而自豪,也不必将整个内存管理留给开发人员的主要原因。
将未使用的指针设置为 NULL 是一种防御方式,可防止出现悬空指针错误。如果悬空指针在被释放后被访问,您可能会读取或覆盖随机内存。如果访问空指针,大多数系统会立即崩溃,并立即告诉您错误是什么。
对于局部变量,如果指针在被释放后不再被访问是“明显的”,这可能有点无意义,所以这种风格更适合成员数据和全局变量。即使对于局部变量,如果函数在内存释放后继续执行,这可能是一个好方法。
要完成样式,您还应该在为指针分配真正的指针值之前将指针初始化为 NULL。
补充一下其他人所说的,使用指针的一种好方法是始终检查它是否是有效指针。就像是:
if(ptr)
ptr->CallSomeMethod();
在释放指针后将指针显式标记为 NULL 允许在 C/C++ 中进行这种使用。
设置一个指向 NULL 的指针是为了防止所谓的双重释放——当 free() 为同一个地址多次调用而不重新分配该地址的块时的情况。
双释放会导致未定义的行为——通常是堆损坏或立即使程序崩溃。为 NULL 指针调用 free() 什么都不做,因此保证是安全的。
因此,除非您现在确定指针在 free() 之后立即或很快离开范围,否则最佳实践是将指针设置为 NULL,以便即使再次调用 free(),它现在也会被调用为 NULL 指针和未定义的行为被回避。
这个想法是,如果您在释放不再有效的指针后尝试取消对它的引用,那么您希望失败(segfault)而不是默默地和神秘地失败。
不过要小心。如果取消引用 NULL,并非所有系统都会导致段错误。在(至少某些版本的)AIX 上,*(int *)0 == 0,并且 Solaris 具有与此 AIX“功能”的可选兼容性。
对于原始问题:释放内容后直接将指针设置为NULL完全是浪费时间,前提是代码满足所有要求,完全调试并且永远不会再次修改。另一方面,当有人在 free() 下轻率地添加了一个新代码块时,当原始模块的设计不正确时,以及在它的情况下,防御性地清空一个已释放的指针可能非常有用-编译但不做我想要的错误。
在任何系统中,都有一个无法实现的目标,即最容易实现正确的事情,以及不准确测量的不可降低的成本。在 C 语言中,我们提供了一套非常锋利、非常强大的工具,这些工具可以在熟练工人的手中创造出许多东西,并且在处理不当时会造成各种隐喻性伤害。有些很难理解或正确使用。而人们,天生厌恶风险,会做一些不合理的事情,比如在调用 free 之前检查指针的 NULL 值……
测量问题是,每当您尝试将好与差分开时,情况越复杂,您就越有可能得到模棱两可的测量结果。如果目标是只保留好的做法,那么一些模棱两可的做法就会被扔掉,而实际上并不好。如果你的目标是消除不好的东西,那么模棱两可的东西可能会一直存在。这两个目标,只保留好的或消除明显不好的,似乎是截然相反的,但通常有第三组既不是一个也不是另一个,两者兼而有之。
在向质量部门提出案例之前,请尝试查看错误数据库以查看无效指针值导致必须记录的问题的频率(如果有的话)。如果您想真正有所作为,请找出您的生产代码中最常见的问题,并提出三种方法来防止它
始终建议使用NULL声明指针变量,例如,
int *ptr = NULL;
假设ptr指向0x1000内存地址。使用后free(ptr)
,始终建议通过再次声明为NULL来使指针变量无效。例如:
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.
由于您有一个质量保证团队,让我补充一点关于 QA 的小问题。一些用于 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 要求。
长话短说:您不想意外(错误地)访问您已释放的地址。因为,当您释放地址时,您允许将堆中的地址分配给其他应用程序。
但是,如果您没有将指针设置为 NULL,并且错误地尝试取消引用指针,或者更改该地址的值;你仍然可以做到。但不是您在逻辑上想要做的事情。
为什么我仍然可以访问我已释放的内存位置?因为:你可能已经释放了内存,但是指针变量仍然有关于堆内存地址的信息。因此,作为防御策略,请将其设置为 NULL。