|
|
1
25
第二个更重要:重新使用释放的指针可能是一个微妙的错误。你的代码一直在工作,然后没有明确的原因崩溃,因为一些看起来不相关的代码写在内存中,重复使用的指针正好指向。 我曾经不得不做一个 真正地 其他人写的小车程序。我的直觉告诉我,许多错误都与释放内存后不小心继续使用指针有关;我修改了代码,在释放内存后将指针设置为空,并且 巴姆 ,空指针异常开始出现。在我修复了所有的空指针异常之后,代码突然 许多的 更加稳定。
在我自己的代码中,我只调用自己的函数,它是free()的包装器。它接受一个指向指针的指针,并在释放内存后使指针为空。在它叫自由之前,它叫
我的代码也做其他事情,例如(仅在调试构建中)在分配内存后立即用一个明显的值填充内存,在调用
编辑:根据请求,下面是示例代码。
评论:
您可以在第二个函数中看到,我需要检查两个资源指针,看看它们是否为空,然后调用
我的实际函数名比较简洁,但是我尝试为这个示例选择自文档化的名称。另外,在我的实际代码中,只有调试代码才能用值填充缓冲区
下面是一个使用
使用示例:
|
|
|
2
7
我不这么做。我不特别记得如果我这样做了会更容易处理的错误。但这真的取决于你如何写你的代码。大约有三种情况下我可以释放任何东西:
在第三种情况下,将指针设置为空。这并不是因为你要释放它,而是因为它是可选的,所以null是一个特殊的值,意思是“我没有”。 在前两种情况下,在我看来,将指针设置为空似乎是忙于没有特定目的的工作:
如果有一个bug,在释放指针后试图取消对它的引用,那么将指针置空会使其更加明显。如果不使指针为空,则取消引用可能不会立即造成损害,但从长远来看是错误的。 指针为空也是正确的 朦胧的 你的双倍自由。如果将指针设为空,则第二个free不会立即造成损害,但从长远来看是错误的(因为它暴露了对象生命周期被破坏的事实)。释放时,可以断言事物为非空,但这将导致以下代码释放包含可选值的结构:
代码告诉你的是你做得太过分了。应该是:
我说,如果指针是 想象上的 保持可用。如果它不再可用,最好不要让它看起来像是假的,输入一个可能有意义的值,比如null。如果要引发页面错误,请使用一个平台相关的值,该值不是可终止的,但其他代码不会将其视为特殊的“一切正常”值:
如果在系统中找不到任何此类常量,则可以分配不可读和/或不可写的页,并使用该页的地址。 |
|
|
3
3
如果不将指针设置为空,那么应用程序继续以未定义的状态运行并在稍后完全不相关的点崩溃的可能性就不小。然后,在发现之前,您将花费大量时间调试一个不存在的错误,这是之前的内存损坏。 我将指针设置为null,因为与未设置为null相比,您更早找到错误的正确位置的可能性更大。第二次释放内存的逻辑错误仍有待考虑,而您的应用程序在具有足够大偏移量的空指针访问上不会崩溃的错误在我看来完全是学术性的,尽管并非不可能。 结论:我倾向于将指针设置为空。 |
|
|
4
3
答案取决于(1)项目规模,(2)代码的预期寿命,(3)团队规模。 在一个生命周期很短的小项目中,可以跳过将指针设置为空,然后进行调试。 在一个大型的长寿命项目中,有充分的理由将指针设置为空: (1)防御性编程总是好的。你的代码可能没问题,但隔壁的初学者可能仍在与指针作斗争 (2)我个人认为,所有变量在任何时候都应该只包含有效值。删除/释放后,指针不再是有效值,因此需要将其从该变量中移除。将其替换为null(唯一始终有效的指针值)是一个很好的步骤。 (3)代码永不消亡。它总是被重用,而且常常以你写它的时候没有想到的方式被重用。您的代码段可能最终在C++上下文中编译,并可能被移到析构函数或被析构函数调用的方法。即使对于经验丰富的程序员来说,正在被破坏的虚拟方法和对象的交互也是一个微妙的陷阱。 (4)如果您的代码最终在多线程上下文中使用,其他线程可能会读取该变量并尝试访问它。当遗留代码在web服务器中包装和重用时,常常会出现这样的上下文。因此,释放内存的更好方法(从偏执的角度)是(1)将指针复制到局部变量,(2)将原始变量设置为空,(3)删除/释放局部变量。 |
|
|
5
2
如果指针将被重用,则在使用后应将is设置回0(空),即使它所指向的对象没有从堆中释放出来。这允许对空值进行有效的检查,比如if(p){//do something}。另外,仅仅因为释放了指针指向的地址的对象,并不意味着指针在调用delete关键字或free函数后设置为0。 如果指针只使用一次,并且是使其成为本地指针的作用域的一部分,则不需要将其设置为null,因为它将在函数返回后从堆栈中释放。 如果指针是成员(结构或类),则在再次释放双指针上的一个或多个对象以进行有效的空检查后,应将其设置为空。 这样做有助于减轻诸如“0xcdcd…”等无效指针带来的麻烦。因此,如果指针是0,那么您知道它没有指向地址,并且可以确保对象从堆中释放。 |
|
|
6
1
两者都非常重要,因为它们处理的是未定义的行为。您不应该在程序中留下任何未定义行为的方法。两者都可能导致崩溃、数据损坏、细微的错误以及任何其他不良后果。 两者都很难调试。两者都是不可避免的,尤其是在复杂的数据结构中。不管怎样,如果你遵循以下规则,你会过得更好:
|
|
|
7
1
在C++中,可以通过实现自己的智能指针(或者从现有的实现中获得)来实现这两个目标,并实现类似的操作:
或者,在C语言中,至少可以提供两个效果相同的宏:
|
|
|
8
1
这些问题通常只是更深层次问题的症状。这可能发生在所有需要获取和更高版本的资源上,例如内存、文件、数据库、网络连接等。核心问题是,由于缺少代码结构,您丢失了资源分配的跟踪,在代码库中抛出了随机malloc并释放了所有资源。 把代码整理好,不要重复。把相关的东西放在一起。只做一件事,做好它。分配资源的“模块”负责释放资源,并且必须提供一个函数来执行此操作,这样也可以保持对指针的关注。对于任何特定的资源,您只有一个分配它的位置和一个释放它的位置,两者都很接近。 假设要将字符串拆分为子字符串。直接使用malloc(),您的函数必须处理所有事情:分析字符串、分配适当的内存量、复制其中的子字符串,以及和。使函数变得足够复杂,而不是您是否会失去对资源的跟踪,而是何时。 第一个模块负责实际的内存分配:
在整个代码库中,只有malloc()和free()被调用。 然后我们需要分配字符串:
它们注意需要len+1,并且释放时指针设置为空。提供另一个复制子字符串的函数:
如果index和len在src字符串中,则需要小心,并在需要时对其进行修改。它将为dst调用stringalloc,并关心dst是否正确终止。 现在可以编写分割函数了。您不必再关心低级细节,只需分析字符串并从中取出子字符串。大多数逻辑现在都在它所属的模块中,而不是混合在一起形成一个巨大的怪物。 当然,这个解决方案也有自己的问题。它提供抽象层,每一层在解决其他问题的同时,都有自己的一组抽象层。 |
|
|
9
0
在你试图避免的两个问题中,没有一个真正“更重要”的部分。如果你想编写可靠的软件,你真的,真的需要避免这两种情况。这也很有可能是上述任何一种情况都会导致数据损坏,使您的web服务器瘫痪,以及其他有趣的事情。 还有一个重要的步骤要记住——释放指针后将其设置为空,这只是工作的一半。理想情况下,如果您使用这个习惯用法,您还应该将指针访问包装成如下内容:
仅仅将指针本身设置为null只会在不合适的地方导致程序崩溃,这可能比无声地破坏数据要好,但这仍然不是您想要的。 |
|
|
10
0
可能不符合标准,但是很难找到一个不将其定义为导致崩溃或异常的非法操作(根据运行时环境而定)的实现。 |
|
|
Ofek Pintok · 释放C中指针指针的动态内存 7 年前 |
|
|
brneuro · 让valgrind开心vs避免segfault 7 年前 |
|
|
Carpetfizz · 非动态数组何时在结构中释放 8 年前 |