即使有时它似乎起作用,但使用空引用的方法或属性总是错误的。
FreeAndNil
确实不能用于检测双重释放。在已经为空的变量上调用 FreeAndNil
是安全的。由于它是安全的,所以它不能帮助你检测任何问题。
这不是陈旧的指针错误,而是空引用错误。陈旧的指针错误是当您释放了一个对象但没有清除所有引用它的变量时发生的。然后该变量仍然保留着对象的旧地址。这些非常难以检测。您可以像这样获得此类错误:
MStr := TMemoryStream.Create;
MStr.Free;
MStr.Size := 0;
你也可以获取一个像这样的:
MStr := TMemoryStream.Create;
OtherStr := MStr;
FreeAndNil(MStr);
OtherStr.Size := 0;
在释放引用了对象
MStr
之后使用
MStr.Size
是错误的,应该引发异常。是否引发异常取决于实现方式。也许会,也许不会,但这并非随机的。
如果您正在寻找双重释放错误,则可以使用FastMM提供的调试工具,正如其他人所建议的那样。它的工作方式不是实际将内存释放回操作系统,甚至不是释放回Delphi的内部自由内存池。相反,它将已知的坏数据写入对象的内存空间中,因此当您看到这些值时,就会知道您正在从已经释放的东西中读取。它还修改了对象的VMT,以便下次在该对象引用上调用虚拟方法时,您将获得可预测的异常,并且它甚至告诉您尝试使用哪个被认为已释放的对象。当您再次尝试释放对象时,它不仅可以告诉您已经释放了它,而且还可以告诉您第一次释放的位置(带有堆栈跟踪)和分配位置。它还收集这些信息以报告内存泄漏,其中您释放了一个对象少于一次,而不是多次。
还有一些习惯可以用来避免未来代码的问题:
- 减少全局变量的使用。全局变量可以被程序中的任何代码修改,迫使您在使用它时思考,“这个变量的值是否仍然有效,还是某些其他代码已经释放了它?”当您限制变量的作用范围时,减少了您需要考虑的程序代码量,以寻找变量没有您预期的值的原因。
- 明确对象的所有者。当有两个代码片段都可以访问同一个对象时,您需要知道哪个代码片段拥有该对象。它们可能各自具有用于引用该对象的不同变量,但仍然只有一个对象。如果一个代码片段在其变量上调用
FreeAndNil
,那么它仍然会保留另一个代码片段的变量不变。如果另一个代码片段认为自己拥有该对象,则会遇到麻烦。(这个所有者的概念不一定与
TComponent.Owner
属性绑定。它可能是程序的一般子系统。)
- 不要保持对您不拥有的对象的持久引用。如果您不保留长期引用对象的引用,则无需担心这些引用是否仍然有效。唯一的持久引用应该在拥有该对象的代码中。需要使用该对象的任何其他代码应该将引用作为输入参数接收,使用该对象,然后在返回其结果时丢弃引用。