在FreeAndNil之后使用对象时会发生什么?

时间:2008-12-12 21:21:19

标签: delphi

在我的Delphi7中这段代码

var MStr: TMemoryStream;
...
FreeAndNil(MStr);
MStr.Size:=0; 

在模块“Project1.exe”中的地址0041D6D1处生成AV:访问冲突。读取地址00000000。 但有人坚持认为不管怎么说都不应该提出任何例外。他还说他的Delphi 5确实没有例外。他称之为“陈旧的指针错误”。 换句话说,他说FreeAndNil不能用作调试器来检测释放对象或使用释放对象的双重尝试。

任何人都可以开导我吗?如果这个引发错误(总是/随机)或程序应该没有问题地运行这个错误?

由于


我问这个是因为我相信我的程序中有一个“双重免费对象”或“免费且重新访问”的错误。在释放对象后,如何用零填充分配给对象的内存?我希望这种方式通过获取和AV来检测bug的位置。 最初,我希望如果我将对象设置为FreeAndNil,我将在尝试重新访问它时始终获得AV。

6 个答案:

答案 0 :(得分:21)

使用null引用的方法或属性总是错误的,即使它似乎有时会起作用。

FreeAndNil确实无法用于检测双重释放。在已经为零的变量上调用FreeAndNil是安全的。由于它是安全的,它无助于您检测任何东西。

这不是一个陈旧的指针错误。这是一个空引用错误。一个陈旧的指针错误就是你释放了一个对象,但清除了所有引用它的变量。然后变量仍然保存对象的旧地址。那些很难察觉。你可以得到这样的错误:

MStr := TMemoryStream.Create;
MStr.Free;
MStr.Size := 0;

你也可以这样:

MStr := TMemoryStream.Create;
OtherStr := MStr;
FreeAndNil(MStr);
OtherStr.Size := 0;

在释放引用的对象MStr.Size后使用MStr是一个错误,它应该引发异常。 是否引发异常取决于实现。也许它会,也许它不会。但这不是随机的。

如果您正在搜索双重免费错误,则可以使用FastMM提供的调试助手,正如其他人所建议的那样。它的工作原理是不将内存释放回操作系统,甚至回到Delphi的内部空闲内存池。相反,它将已知坏数据写入对象的内存空间,因此当您看到这些值时,您将知道您正在读取已经释放的内容。它还修改了对象的VMT,以便下次在该对象引用上调用虚方法时,您将获得可预测的异常,它甚至会告诉您尝试使用哪个被释放的对象。当您尝试再次释放该对象时,它不仅可以告诉您已经释放它,还可以告诉您第一次释放它(使用堆栈跟踪)以及分配它的位置。它还收集该信息以报告有关内存泄漏的信息,其中您释放的对象 less 而不是更多。

您可以使用习惯来避免将来的代码出现问题:

  • 减少全局变量的使用。整个程序中的任何代码都可以修改一个全局变量,强迫您在使用它时不知道,“这个变量的值是否仍然有效,或者其他一些代码是否已经释放它?”限制变量的范围时,在查找变量没有预期值的原因时,可以减少程序中必须考虑的代码量。
  • 明确谁拥有一个物体。当有两段代码可以访问同一个对象时,您需要知道拥有对象中的哪些代码片段。它们可能每个都有一个不同的变量用于引用对象,但是那里仍然只有一个对象。如果一段代码在其变量上调用FreeAndNil,那么仍然保持其他代码的变量不变。如果其他代码认为它拥有该对象,那么您就遇到了麻烦。 (所有者的概念不一定与TComponent.Owner属性相关联。不需要拥有它的对象;它可以是程序的一般子系统。)< / LI>
  • 不要持久引用您不拥有的对象。如果不保留对对象的长期引用,则不必担心这些引用是否仍然有效。唯一的持久引用应该在拥有该对象的代码中。需要使用该对象的任何其他代码都应该接收引用作为输入参数,使用该对象,然后在返回结果时丢弃该引用。

答案 1 :(得分:10)

从我所看到的,这段代码应该总是导致错误。 FreeAndNil显式地将传递的值设置为Nil(也称为0),因此在尝试取消引用对象时,绝对应该获得访问冲突。

答案 2 :(得分:8)

只是让问题复杂化:

如果您调用的方法是静态(非虚拟)方法并且它本身不调用任何虚方法,也不访问该对象的任何字段,即使已设置对象引用,也不会出现访问冲突到NIL。

原因是访问冲突是由解除引用自指针(在本例中为NIL)引起的,但只有在访问字段或对象的VMT以调用虚方法时才会发生。

这只是规则的一个例外,你不能调用我想在这里提到的NIL对象引用的方法。

答案 3 :(得分:5)

如果将指针设置为nil,则不应再使用它。但是如果你有另一个指向同一个对象的指针,你可以使用它而不需要AV,因为这个指针仍指向对象地址而不是nil。

此外,释放对象不会清除该对象使用的内存。它只是标志着它没有被使用。这就是你想要AV的原因。如果为另一个对象分配了释放的内存,您将获得一个AV,因为它不再包含似乎有效的数据。

FastMM4有一些可以在调试时使用的设置,可以检测这些情况。来自FsatMM4Options.inc:

  

{设置以下选项以对所有内存块进行大量检查。所有    块用标题和尾部填充,用于验证    堆的完整性。释放的块也被清除以确保它们    被释放后不能重复使用。此选项会降低内存操作速度    显着,应该只用于调试应用程序    覆盖内存或重用已释放的指针。设置此选项    自动启用CheckHeapForCorruption并禁用ASMVersion。    非常重要:如果启用此选项,您的应用程序将需要    FastMM_FullDebugMode.dll库。如果没有这个库,你会的    启动时出错。}
  {$ define FullDebugMode}

同一档案的另一个引用:

  

FastMM总是捕获两次释放相同内存块的尝试......

由于delphi使用Delphi 2007(2006?)中的FastMM,如果你尝试双重对象,你应该会收到错误。

答案 4 :(得分:1)

Thomas Mueller :您尝试过虚拟类方法吗?构造函数是一种虚方法,但您可以根据类型调用它 - 而不是实例。这意味着即使某些特定的虚拟方法也不会在空引用上引起AV:D

Vegar :你不可能更正确! FastMM是有史以来最好的工具,帮助我追踪这种错误。

答案 5 :(得分:1)

EurekaLog博客在2009年4月发表了一篇很好的文章:

Why should you always use FreeAndNil instead of Free.