Delphi TStringList释放引起异常

4

Consider this short Delphi procedure:

procedure TfrmXQuery.FieldListFillFromDefault;
var
  field_list: TStringList;
begin
  try
    if x <> '' then begin
      field_list := TStringList.Create;
      {do some stuff with field_list}
    end;
  finally
    if field_list <> NIL then 
    begin
      field_list.Free;
    end;
  end;
end;

当我在Delphi 3中运行此代码,并将x设置为'',以便field_list从未创建时,
  1. 为什么field_list <> NIL
  2. 对象没有初始化为NIL吗?
  3. 如果它不是NIL,那它是什么?
  4. 如果它未被分配且不是NIL,我如何知道是否需要Free它?Assigned函数无法告诉我:if Assigned(an_object)等同于if an_object = NIL

1
在释放内存时,绝对不需要检查是否已经分配了引用。Free 已经完成了这个操作,如果已经分配了引用,它会调用 Destroy。这就是为什么你永远不应该调用 Destroy。 - Sir Rufo
1
编译器没有警告你这段代码吗?永远不要忽略编译器的诊断信息。 - Rob Kennedy
1
这个答案会对你有帮助:https://dev59.com/Tmoy5IYBdhLWcg3wcNrh#8550628 - David Heffernan
1
@SirRufo 绝对正确。if Assigned(Foo) then Foo.Free 本质上等同于 if Assigned(Foo) then if Assigned(Foo) then Foo.Destroy,因为 X.Free 基本上是 if Assigned(X) then X.Destroy - Andreas Rejbrand
@AndreasRejbrand 但是在这个问题的示例中,Assigned(foo)将返回true,但是foo未初始化,因此foo.Free将失败。 - Jonathan Elkins
1
@JonathanElkins:确实,那将会惨败,但那是一个不同的问题(在实践中更糟糕,当然)。 - Andreas Rejbrand
2个回答

8
问题在于,如果 x = ''finally 语句还是会执行。由于只有当 x <> '' 时才会初始化 field_list,因此在那之前它是一个随机的内存位置,因为它是一个未初始化的局部变量。这个随机值允许调用 field_list.free,因为它不等于 nil。(Delphi 不会初始化局部变量(在函数或过程中声明的变量)。)
var
  somevar: sometype;    
begin
  // at this point, somevar is just a chunk of memory that
  // holds whatever happens to be in that chunk
  somevar := nil;         // now somevar = a specific value you can test

  // other code
end;

如果你的代码结构正确,就不需要测试 <> nil(正如其他评论中所指出的那样)。

procedure TfrmXQuery.FieldListFillFromDefault;
var
  field_list  : TStringList;
begin
  if x <> '' then 
  begin
    field_list := TStringList.Create;
    try
      {do some stuff with field_list}
    finally
      field_list.Free;
    end;
  end;
end;

(如果您打开提示和警告,编译器会提示您field_list可能未被初始化,这将帮助您自己解决此问题。)

不需要使用“if field_list <> NIL”。TObject.Free已经测试了这一点 - 实际上,这是它的全部目的。所有这些做的就是说'如果field_list <> NIL,则如果field_list <> NIL,则field_list.Destroy;'请参见http://docwiki.embarcadero.com/Libraries/XE2/en/System.TObject.Free - Gerry Coll
@Gerry:好眼力。我在两个代码片段中都忘了删除它们。谢谢。 :-) - Ken White
1
Delphi 3没有初始化本地变量。任何版本的Delphi都没有这样做过。 - David Heffernan
4
if x <> ''语句移到第一行,并用try/finally块将随后的代码封装起来,会更加合乎逻辑。 - LU RD
1
David和LURD: 两点都很有道理。答案相应进行了修订。自己注意:晚上发布帖子时不要想太多,也不要太累了。 :-) - Ken White
谢谢大家。我简直不敢相信我一直在使用Delphi,而且一直以为对象在任何地方都被初始化为NIL。我还会打开提示并特别注意“变量可能未初始化”的消息。 - Jonathan Elkins

-3

问题的答案:

  1. 为什么 field_list <> NILDelphi 不会初始化本地对象。详见: 为什么对象不默认为 nil?Delphi 变量默认被赋初始值吗?

  2. 对象不是作为 NIL 来初始化的吗? 全局对象:是。局部对象:否。

  3. 如果它不是 NIL,那它是什么呢?无效指针。

  4. 如果它未被分配并且不是 NIL,我怎样才能确定是否需要 Free 呢? 你需要重新组织你的代码(见下文)。 Assigned 函数不能告诉我:if Assigned(an_object) 等同于 if an_object = NILAssigned 用于测试空(未分配)指针或程序变量。<--来自 Delphi 3 文档。 未初始化的局部对象没有被分配为 NIL,因此如果 an_object 是局部的且从未使用过(赋值 NIL 就是使用了对象),则 Assigned(an_object) 返回 TRUE。

由于过程中的本地对象未初始化为NIL,因此我已经修改了问题代码,将所有本地对象分配为NIL。我在程序的最开始进行这些分配,以便如果本地对象从未创建,Free不会出错。我还展示了原始问题中遗漏的错误跟踪代码:

procedure TfrmXQuery.FieldListFillFromDefault;
var
  field_list: TStringList;
  some_other_object: TAnotherObject;
begin
  try
    try
      field_list := NIL;
      some_other_object := NIL;
      if x <> '' then begin
        field_list := TStringList.Create;
        {do some stuff with field_list}
      end;
      {...}
      some_other_object := TSomeOtherObject.Create;
      {...}
    except
      On E : Exception do begin
        ErrorTrackingRoutine(unit_name, name, 'FieldListFillFromDefault', E.message);
      end;
    end;
  finaly
    field_list.Free;
    some_other_object.Free;
  end;
end;

整个程序例程都被 try...except 保护。如果创建了 field_listsome_other_object,它们将被释放。它们在最开始时被赋值为 NIL,因此即使在它们被创建之前发生运行时错误,在 'try...finally` 块中释放它们也不会引发错误。

1
那个初始化为nil是浪费的。将try/finally移到if语句内部。或者更好的做法是,在开始时将if转换为guard:如果x='',则退出;此外,这并没有回答问题。 - David Heffernan
2
是的,我第一次确实读了那段文字。使用非标准的try/finally只会让读者更难理解代码。不要这样做。 - David Heffernan
1
不标准的是 try 出现在构造函数调用之前。标准的模式是:o := TObj.Create; try o.foo; finally o.Free; end; 遵循这个模式可以让读者更容易理解代码。因此我会使用一个守卫语句来处理 x='',然后使用标准的 try/finally。 - David Heffernan
1
然后赋值操作不会发生,try 块也不会被执行,异常会沿着调用栈向上抛出。 - David Heffernan
1
没有异常块。答案中的代码没有执行您在这些评论中描述的任何操作。它也没有回答所提出的问题。再来一次踩。 - David Heffernan
显示剩余7条评论

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