记录类型的TList泄漏内存

时间:2015-03-01 20:45:23

标签: delphi record tlist

对于每个创建的TMyRecord中的每个字符串实例,代码看起来大致类似于以下泄漏内存。我认为我必须访问每个记录并以某种方式释放它 - 可以在不填写每个字符串的情况下完成吗?

function TMyForm.RecordFromThing(thing): TMyRecord;
begin
   result.StringVal1 = thing.SomeProperty;
   result.StringVal2 = thing.SomeOtherProperty;
end;

function TMyForm.RecordsFromItems: TList<TMyRecord>;
begin
   result := TList<TMyRecord>.Create;
   for thing in things do
   begin
     result.Add(RecordFromThing(thing));
   end;
end;

procedure TMyForm.Button1Click(Sender: TObject);
var Items: TList<TMyRecord;
begin

  Items := RecordsFromItems;
  try
    //Stuff
  finally
    // What goes here to free those records?
    Items.Clear;
    Items.Free;
  end

end;

根据David的要求,这是从我的应用程序中剪切并粘贴的实际代码。如果我运行Button1Click,则FastMM报告字符串内存泄漏。

  type TPremiumPaymentInstruction = record
    SupplierID: string;
    FirstName: string;
    LastName: string;
    PolicyNo: string;
    CarrierName: string;
    PayAmount: string;
    DueDate: string;
    PayMethod: string;
    Comments: string;
    Payee: string;
    Address: string;
    Address2: string;
    City: string;
    State: string;
    Zip: string;
    InRe: string;
    BankName: string;
    BankABA: string;
    AccountName: string;
    AccountNo: string;
    CreditTo: string;
  end;

function TPremiumPaymentManager.RecordFromItem(Item: TListItem): TPremiumPaymentInstruction;
var PolicyID: integer;
    Instructions: TDataSet;

    function FormatNote(PolicyNo, First, Last: string): string;
    begin
      result := 'Policy# ' + PolicyNo + '; Insured Name: ' + Last + ' ' + First;
    end;

begin

  FillChar(result, SizeOf(result), 0);

  result.SupplierID  := Item.Caption;
  result.FirstName   := Item.SubItems[INSURED_FIRST_NAME_COLUMN];
  result.LastName    := Item.SubItems[INSURED_LAST_NAME_COLUMN];
  result.PolicyNo    := Item.SubItems[POLICY_NUMBER_COLUMN];
  result.CarrierName := Item.SubItems[CARRIER_NAME_COLUMN];
  result.PayAmount   := Item.SubItems[ACTUAL_COLUMN];
  result.DueDate     := Item.SubItems[DATE_DUE_COLUMN];
  result.PayMethod   := Item.SubItems[PAYMENT_METHOD_COLUMN];

  PolicyID := GetSingleValue('SELECT PolicyID FROM PremiumsDue WHERE PremiumID = :PremiumID;', [(Item as TAdvListItem).KeyValue], 0);
  Instructions := GetDS('SELECT I.* FROM Policies P INNER JOIN CarrierPaymentInstructions I ON P.CarrierID = I.CarrierID AND P.PaymentInstruction = I.InstructionDescription WHERE P.PolicyID = ?;', [PolicyID]);

  try

    Instructions.Open;
    Instructions.First;

    if result.PayMethod = 'Check' then
      begin
        result.Comments := Item.SubItems[PAYMENT_NOTE_COLUMN];
        result.Payee    := Instructions.FieldByName('PayTo').AsString;
        result.Address  := Instructions.FieldByName('Address1').AsString;
        result.Address2 := Instructions.FieldByName('Address2').AsString;
        result.City     := Instructions.FieldByName('City').AsString;
        result.State    := Instructions.FieldByName('State').AsString;
        result.Zip      := Instructions.FieldByName('ZipCode').AsString;
        result.InRe     := FormatNote(result.PolicyNo, result.FirstName, result.LastName);
      end
    else
      begin
        result.BankName    := Instructions.FieldByName('PayTo').AsString;
        result.BankABA     := Instructions.FieldByName('ABANumber').AsString;
        result.Address2    := Instructions.FieldByName('Address2').AsString;
        result.AccountName := Instructions.FieldByName('AccountName').AsString;
        result.AccountNo   := Instructions.FieldByName('AccountNumber').AsString;
        result.CreditTo    := FormatNote(result.PolicyNo, result.FirstName, result.LastName);
      end;

  finally

    Instructions.Free;

  end;

end;

function TPremiumPaymentManager.RecordsFromItems: TList<TPremiumPaymentInstruction>;
var item: TListItem;
begin

  result := TList<TPremiumPaymentInstruction>.Create;

  for item in lvPremiums.Items do
  begin
    if (not (Item as TAdvListItem).Strikeout) and (Item.SubItems[ACTUAL_COLUMN] <> '')  then
      result.Add( RecordFromItem(item) );
  end;

end;

procedure TPremiumPaymentManager.Button1Click(Sender: TObject);
var Items: TList<TPremiumPaymentInstruction>;
begin

  Items := RecordsFromItems;
  Items.Clear;
  Items.Free;

end;

1 个答案:

答案 0 :(得分:4)

字符串是托管类型。编译器负责其分配和释放。编译器编写维护引用计数的代码到字符串数据。当引用计数变为零时,将释放内存。

因此,您不需要释放字符串或确实执行任何显式管理。任何这样做的尝试都可能与编译器执行的操作发生冲突。例如,如果对字符串变量执行原始内存访问,则通过调用FillChar进行查看,然后将绕过引用计数。

你问题中的代码,除了不完整和部分不编译外,基本上都没问题。完成TList<TMyRecord>之后,只需将其释放即可。编译器将生成代码以释放所有引用。甚至不需要清除它。

List := TList<TMyRecord>.Create;
try
  List.Add(...);
  // etc. 
finally
  List.Free;
end;

这就是你所需要的一切。

由于您提出的问题中的代码基本上与此相同,因此我得出结论,泄漏的原因在其他地方。


您知道什么,您的编辑包含对FillChar的详细调用。用

替换它
Result := Default(TPremiumPaymentInstruction);

您始终需要初始化返回值。甚至返回值通常是初始化的托管类型。但不是以你的方式在for循环中进行调用。去搞清楚。无论如何,总是初始化返回值。 Default(T)是你的朋友。