【问题标题】:Delphi Seattle: I get an Invalid Pointer operation when freeing an object I createdDelphi Seattle:释放我创建的对象时出现无效指针操作
【发布时间】:2016-03-02 21:46:30
【问题描述】:

我使用 Delphi Seattle。

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

我在此站点(以及其他站点)中搜索了已针对此问题发布的答案,但它们都有些不同。根据这些讨论,我的代码应该可以工作,但显然有些地方不太对劲。

所以,我需要帮助...

执行流程:

a) 在表单 fmLoanRequest 中,我创建了一个基于 Class TStorageLoan(TLoan 的子类)的对象。构造函数将所有类型的值加载到对象的一些属性中(现在在这里显示)。

b) 稍后,我将对象的地址传递给另一种形式 (fmLoan) 到适当的公共变量。 fmLoan 是所有用户处理 Loan 内容的形式。请注意,当我们在 fmLoan 中时,fmLoanRequest 保持原样。当 fmLoan 关闭时,我们将返回 fmLoanrequest。

c) 显示 fmLoan 表单(并显示对象中的数据 - 一切正常)。

d) 当关闭 fmLoan 时,会调用一个过程来释放 Loan 对象 - 如果它已被分配(参见 second 代码 sn-p 的第 10 行)。这似乎工作正常(没有错误)。

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

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

form 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;

form 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 行 - 第一个代码 sn-p)并希望最好。这根本不是我正确编码的理念!

我是不是走错路了?

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

任何帮助将不胜感激!

【问题讨论】:

标签: delphi delphi-10-seattle


【解决方案1】:

很明显,您两次释放 Loan 对象,这就是您收到错误的原因。你只需要释放它一次。 fmLoanRequests 创建了对象,但是你说它可以在 fmLoan 关闭之前关闭,所以 fmLoan 应该获得对象的所有权并在 fmLoan 关闭时释放它。当fmLoanRequest 关闭时,根本不释放对象。

替代方法是定义TLoan 及其后代实现的ILoan 接口,然后传递ILoan 而不是直接传递TLoan。接口是引用计数的,所以 Loan 对象将被自动释放,并且只会在 fmLoanRequestsfmLoan 都释放对它的引用之后释放一次。

【讨论】:

  • 我误解了 Assigned() 的工作方式有点像 Java 中的垃圾收集器(测试 Referenced Object 是否有其他引用。这显然是不正确的 - Assigned() 测试是否引用变量仍然保存一些地址(有效与否)。
  • Delphi 中没有垃圾回收。 Assigned() 仅检查指定的指针是否为 nil,仅此而已。但是,在移动平台上,Delphi 确实有 ARC (auto reference counting) 用于对象,TObject 在 ARC 系统上具有 RefCountDisposed 属性。
【解决方案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.

这会输出以下内容:

错误的 真的 真的

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

您的错误是您认为Assigned 测试对象是否已被销毁。它不这样做。它只是测试引用变量是否为nil。让我们再更详细地看一下代码。

obj := TObject.Create;

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

obj.Free;

这会破坏obj 所指的对象。析构函数运行,然后内存被销毁。此行执行后,对象已被销毁,但obj 仍然指的是已销毁且现在无效的内存。这就是为什么 Assigned(obj) 产生 true 的原因。

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

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

我为一个不同的问题写了一个关于整个主题的详细答案。我建议您阅读该答案:https://stackoverflow.com/a/8550628/505088

您将带走的一点是像

这样的代码
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 垃圾收集器的测试,以查看是否有任何其他位置正在引用此对象(如果是,请不要理会;如果不是,可以被删除)。我的错!我感谢所有的解释和示例 - 我的代码会更好!
猜你喜欢
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-29
  • 2012-04-24
  • 2014-09-08
  • 1970-01-01
相关资源
最近更新 更多