Delphi Seattle:在释放我创建的对象时,我得到一个无效的指针操作

时间:2016-03-02 21:46:30

标签: delphi delphi-10-seattle

我使用Delphi Seattle。

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

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

所以,我需要帮助...

执行流程:

a)以fmLoanRequest的形式,我基于Class TStorageLoan(TLoan的一个子类)创建一个对象。构造函数将所有类型的值加载到对象的某些属性中(现在显示在此处)。

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

c)显示fmLoan表单(并显示对象中的数据 - 一切运行良好)。

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

e)执行下面第14行中的代码时出现“无效指针操作”错误:(如果已分配(oLoan),则为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行 - 第一个代码段)并希望获得最佳效果。这完全不是我正确编码的理念!

我是以错误的方式去做的吗?

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

任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:2)

很明显,您正在释放贷款对象两次,这就是您收到错误的原因。你只需要释放一次。 fmLoanRequests创建了对象,但您说它可以在fmLoan关闭之前关闭,因此fmLoan应取得对象的所有权,并在fmLoan关闭时释放它。 <{1}}关闭时,请勿释放对象。

另一种方法是定义一个fmLoanRequest接口,ILoan和后代实现,然后直接传递TLoan而不是ILoan。接口是引用计数的,因此贷款对象将自动释放,并且只有在TLoanfmLoanRequests释放对它的引用后才会释放一次。

答案 1 :(得分:1)

  

我添加了这一行,以确保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)产生真实的原因。

  

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

这是一个有趣的观点。实际上,只要使用引用变量,就会使用指针。尽管该语言隐藏了这一事实,但对象引用变量只不过是指向堆上分配的内存的指针。我们使用术语引用而不是指针,但实际上这些是相同的东西。它们的行为相同,赋值运算符具有相同的语义,您仍然有可能泄漏,双重释放,释放后访问以及指针的所有其他陷阱。因此,尽管您没有明确地使用指针,但仍然需要考虑对象引用变量,就好像它们是指针一样。

我为这个问题写了一个关于这个问题的详细答案。我建议您阅读答案: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的电话以“以防万一”,那么您几乎肯定会做出严重错误。