Delphi Seattle:当我释放一个我创建的对象时,出现了无效指针操作。

5

我使用Delphi Seattle。

当我尝试释放我创建的对象时,出现了问题。

我已经在这个网站(以及其他网站)搜索了已发布的答案,但它们都有点不同。根据这些讨论,我的代码应该是可以工作的,但显然还有一些问题。

所以,我需要帮助……

执行流程:

a)在fmLoanRequest表单中,我基于TLoan类创建一个对象TStorageLoan(TLoan的子类)。构造函数将各种值加载到对象的某些属性中(此处未显示)。

b)稍后,我将对象的地址传递给另一个表单(fmLoan)的一个适当的公共变量。fmLoan是所有用户与贷款内容交易的表单。请注意,在我们进入fmLoan时,fmLoanRequest保持不变。当fmLoan关闭时,我们将返回fmLoanrequest。

c)显示fmLoan表单(并显示对象中的数据 - 所有这些都正常运行)。

d)关闭fmLoan时,调用一个过程来释放贷款对象 - 如果它被分配了(参见第二个代码片段的第10行)。这似乎没有问题(没有错误)。

e)当执行下面第14行的代码时,会出现“无效指针操作”错误:if Assigned(oLoan) then oLoan.Free;

我添加了这行代码,以确保如果fmLoan由于某种原因没有处理对象,则对象将被释放。我意识到此时对象已经被释放,但是“if Assgned()”不应该防止不必要的释放对象吗?

来自表单fmLoanRequest的部分代码(我添加了一些行号以供参考)

1  // In form fmLoanRequest 
2  // Create new Loan Object (from a Loan sub-class as it happens)
3    // Create the object here; Object address will be passed to fmLoan later for handling.
4    oLoan := TStorageLoan.Create(iNewLoanID);
5  ...
6  ...
7     fmLoan.oLoan := oLoan; // pass the address to the other form
8     fmLoan.show;
9     // User would click the 'btnClose' at this point. See event code below.
10  ...
11  ...
12    procedure TfmLoanRequests.btnCloseClick(Sender: TObject);
13    begin 
14      if Assigned(oLoan) then oLoan.Free; // <--- ERROR HERE 
15      fmLoanRequests.Close;
16  end;

下面是来自表单fmLoan的部分代码(我添加了一些行号作为参考):

1  //Form fmLoan
2  ...
3    public
4      oLoan : TLoan;
5  ... 
6  // In form fmLoan, I call the following upon closing the Form
7  //                 in the OnClick event of the 'btnClose' button. 
8  Procedure TfmLoan.Clear_Loan_Object;
9  begin
10    if Assigned(oLoan) then oLoan.Free; // <-- THIS WORKS FINE
11  end;

我应该尝试不同的方法吗?

我是否应该删除那行代码(第一个代码片段的第14行),然后希望一切都能正常。这并不是我的正确编码哲学!

我的做法是否有误?

注:我显然不使用指针。

任何帮助都将不胜感激!


为什么在使用或释放东西之前不应该使用“if Assigned()”? - J...
你可以在使用oLoan.free的所有地方上使用FreeAndNil(oLoan)。这样,当您期望它时,对Assigned(oLoan)的测试将返回true。 - Freddie Bell
2个回答

2
我添加了这行代码,以确保如果 fmLoan 没有处理它,对象将被释放。我意识到此时对象已经被释放,但是 if Assigned() 不应该防止不必要的对象释放吗?
这是一个关键的误解。考虑以下程序:
{$APPTYPE CONSOLE}

var
  obj: TObject = nil;

begin
  Writeln(Assigned(obj));

  obj := TObject.Create;
  Writeln(Assigned(obj));

  obj.Free;
  Writeln(Assigned(obj));

  Readln;
end.

这将输出以下内容:

FALSE
TRUE
TRUE

请注意,输出的最后一行是TRUE。换句话说,当您通过调用其Free方法销毁对象时,引用变量不会设置为nil

您的错误在于认为Assigned测试对象是否已被销毁。它并没有这样做。它只是测试引用变量是否为nil。让我们再次更详细地查看代码。

obj := TObject.Create;

在这里,我们使用 TObject.Create 创建一个分配在堆上的新对象。我们还将该对象的地址或引用分配给 obj。执行完此行后,obj 是一个包含有效对象地址的引用变量。
obj.Free;

这会销毁obj所引用的对象。析构函数被执行,然后内存被销毁。执行此行代码后,对象已被销毁,但obj仍然指向那个已被销毁且现在无效的内存块。这就是为什么Assigned(obj)返回true的原因。

注意:我显然不使用指针。

这是一个有趣的观点。实际上,每当您使用引用变量时,都在使用指针。尽管语言隐藏了这一事实,但对象引用变量只是指向在堆上分配的内存的指针。我们使用术语“引用”而不是“指针”,但实际上它们是相同的东西。它们的行为完全相同,赋值运算符具有相同的语义,您仍然可能存在泄漏、双重释放、释放后访问以及指针的所有其他缺陷。因此,即使您没有明确使用指针,仍然有必要将对象引用变量视为指针。

我为另一个问题撰写了关于整个主题的详细答案。建议您阅读该答案:https://dev59.com/Tmoy5IYBdhLWcg3wcNrh#8550628

你将会学到的其中一个要点是,像下面这样的代码:
if Assigned(oLoan) then 
  oLoan.Free;

是毫无意义的。 Free 方法还会检查对象引用是否为 nil。这行代码实际上被扩展为:

if Assigned(oLoan) then 
  if Assigned(oLoan) then 
    oLoan.Destroy;

所以,不是

if Assigned(oLoan) then 
  oLoan.Free;

你应该简单地写

oLoan.Free;

现在,回到访问冲突问题。我认为现在很明显,您正在尝试销毁已经被销毁的对象。您不能这样做。您需要重新检查生命周期管理。像“如果fmLoan由于某种原因没有处理它”这样的推理是不够好的。您需要100%确定生命周期管理。您需要确保您的对象仅被销毁一次。我无法看到您所有的代码,因此不想提出具体建议。

有时有用的一种模式是在销毁对象时将对象引用设置为nil。如果对象可能在多个位置被销毁,则可以使用此技术来确保您不会尝试两次销毁它。您甚至可以使用FreeAndNil帮助函数。但是,值得强调的是,如果您不确定是否已经销毁了对象,那通常意味着设计不良。如果您发现自己想要添加对Free的调用以“以防万一”,那么您几乎肯定正在做一些严重错误的事情。


谢谢 - 我理解Assigned()测试相当于Java垃圾回收器的测试,以查看是否有其他位置引用此对象(如果是,则不要动它;如果没有,则可以删除)。我的错误!感谢所有的解释和示例 - 我的代码将因此更好! - Jack N.

2
很明显你释放了 Loan 对象两次,所以才会出现错误。你只需要释放一次即可。fmLoanRequests 创建对象,但是你说它可以在 fmLoan 关闭之前关闭,因此 fmLoan 应该接管对象,并且在 fmLoan 关闭时释放它。当 fmLoanRequest 被关闭时不要释放对象。
另一种选择是定义一个 ILoan 接口,让 TLoan 及其子类来实现,然后传递 ILoan 而不是直接传递 TLoan。接口具有引用计数功能,因此 Loan 对象将自动释放,并且仅在 fmLoanRequests 和 fmLoan 释放对它的引用后释放一次。

我曾经误解了Assigned()的工作方式,认为它有点像Java中的垃圾回收器(测试引用对象是否有其他引用)。显然这是不正确的 - Assigned()测试引用变量是否仍然持有某个地址(有效或无效)。 - Jack N.
Delphi中没有垃圾回收机制。Assigned()仅仅检查指定的指针是否为空,除此之外没有其他作用。然而,在移动平台上,Delphi确实有ARC(自动引用计数)用于对象,并且在ARC系统上,TObject具有RefCountDisposed属性。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接