首页 > 解决方案 > Delphi - TObjectDictionary 似乎没有释放拥有的价值

问题描述

当我使用 doOwnsValues 创建 TObjectDictionary 并将 TStringList 存储为值时,在释放 TObjectDictionary 之后,我仍然可以访问 TStringList。

我修改了此处给出的示例: 使用 Generics.Collections.TObjectDictionary的示例

{$APPTYPE CONSOLE}
{$R *.res}
uses
  Generics.Collections,
  Classes,
  System.SysUtils;


Var
  MyDict  : TObjectDictionary<String, TStringList>;
  Sl      : TStringList;
begin
  ReportMemoryLeaksOnShutdown:=True;
  try
   //here i'm  creating a TObjectDictionary with the Ownership of the 
     Values
   //because in this case the values are TStringList
   MyDict := TObjectDictionary<String, TStringList>.Create([doOwnsValues]);
   try
     //create an instance of the object to add
     Sl:=TStringList.Create;
     //fill some foo data
     Sl.Add('Foo 1');
     Sl.Add('Foo 2');
     Sl.Add('Foo 3');
     //Add to dictionary
     MyDict.Add('1',Sl);


     //add another stringlist on the fly
     MyDict.Add('2',TStringList.Create);
     //get an instance  to the created TStringList
     //and fill some data
     MyDict.Items['2'].Add('Line 1');
     MyDict.Items['2'].Add('Line 2');
     MyDict.Items['2'].Add('Line 3');


     //finally show the stored data
     Writeln(MyDict.Items['1'].Text);
     Writeln(MyDict.Items['2'].Text);
   finally
     //only must free the dictionary and don't need to worry for free the 
TStringList             assignated to the dictionary
     MyDict.Free;
   end;

   Sl.Add('Foo added?');
   Writeln(Sl[0]);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

我希望 S1 不应该被访问。是内存泄漏吗?还是错误?

标签: delphi

解决方案


字符串列表对象Sl 被字典释放。没有内存泄漏。Delphi 的 RTL(字典类)在这里没有错误。

因此,当您Sl.Add('Foo added?');访问一个已被释放的对象时,您的代码中的一个错误。你不应该那样做。

仅仅因为您这次没有得到任何明显的错误症状并不意味着它不是错误。

一个更简单的例子:

type
  TTest = class
    Value: Integer;
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  Test: TTest;
begin
  Test := TTest.Create;
  try
    Test.Value := 123;
  finally
    Test.Free;
  end;
  ShowMessage(Test.Value.ToString); // Bug!! Never do this!
end;

这是一个错误。但无论如何,您可能会收到一个123消息框。

当您释放一个对象时,它的内存区域被声明为可用,以便将来可以将其他数据存储在那里。因此,在您的对象被销毁后,它的字节很可能在您离开它们的地方完好无损。但最终它们将被新数据覆盖,并且随着时间的推移,所有字节都可能发生变化。你永远不知道,也不应该知道:你不再拥有计算机内存的那部分。

当然,销毁一个对象可能不仅仅是放弃它在内存中的位置:操作系统句柄可能会被关闭,临时文件可能会被删除等等。因此,如果你使用一个释放的对象,有很多事情可能会出错,甚至如果您在对象销毁后立即使用该对象。

更糟糕的是,旧对象的内存区域现在​​可能部分或完全属于应用程序中的新对象(或其他类型的数据),因此通过访问幽灵对象的成员,您可能会随机更改这些新对象中的字节. 这可能会在您的应用程序生命周期的任何后期导致细微的随机故障、错误和崩溃。

但是您不必关心这些细节,因为无论如何您都不会使用已释放的对象。

(或者,这样想:如果你使用一个被释放的对象,你就违反了合同,基本上任何事情都可能发生。一种可能性是根本没有任何“奇怪”的事情发生,但这只是运气不好。)


推荐阅读