Приложение Delphi протекает AnsiStrings

Согласно FastMM4, программа Delphi, над которой я работаю, в настоящее время протекает много строк. AnsiStrings, если быть точным:

enter image description here

Приложение (http://sourceforge.net/projects/orwelldevcpp/) используется для утечки гораздо большего количества других типов данных, но FastMM4 может сообщать, где был создан экземпляр, поэтому Мне удалось это исправить. Странно, что FastMM4 не сообщает о местонахождении этих утечек вообще.

Изменить: похоже, это все-таки, см. ответы на исправление. В любом случае, вопрос все еще стоит: как в мире я утечка этих вещей?

Итак, эмм, к сожалению, я понятия не имею, что искать. Я имею в виду, если эти вещи выходят за рамки, они должны быть автоматически освобождены вправо (даже если они находятся в куче)?

Мне удавалось отслеживать несколько утечек случайным комментарием и видеть, что произойдет с подсчетами. Вот пример:

// simply passing it a constant creates a leak...
MainForm.UpdateSplash('Creating extra dialogs...');

procedure TMainForm.UpdateSplash(const text : AnsiString);
begin
  if not devData.NoSplashScreen then // even if this branch is NOT taken
    SplashForm.Statusbar.SimpleText := 'blablabla' + text;
end;

// And even if the function call itself is placed within a NOT taken branch!

Вот еще один пример утечки:

// Passing this constants produces leaks...
procedure TCodeInsList.AddItemByValues(const a, b, c: AnsiString;...);
var
  assembleditem : PCodeIns;
begin
   new(assembleditem);
   assembleditem^.Caption:=a;
   assembleditem^.Line:=b;
   assembleditem^.Desc:=c;
   ...
   fList.Add(assembleditem);
end;

// ... even when calling this on WM_DESTROY!
destructor TCodeInsList.Destroy;
var
  I: integer;
begin
  for I := 0 to fList.Count - 1 do
    Dispose(fList[I]);
  fList.Free;
  inherited Destroy;
end;

// produces leaks!?

Здесь довольно много вопросов о проблемах с строкой, но никто не проясняет , какие шаблоны следует искать. Google также не предоставляет.

Изменить: так, я должен искать прошедшие константы. Но почему?

Итак, какие-нибудь идеи?

Ответ 1

Вам не нужно явно выделять строки. Помимо манипуляции с подсчетом ссылок, могут также течь поля строк объектов или записей. Например,

type
  PRecord = ^TRecord;
  TRecord = record
    S: string;
  end;

procedure TForm1.Button4Click(Sender: TObject);
var
  r: PRecord;
begin
  GetMem(r, SizeOf(r^));
  Initialize(r^);
  r.S := ' ';
  FreeMem(r);

В приведенном выше примере, поскольку память самой записи освобождена, FastMM будет сообщать только о пропущенной строке.


В любом случае FastMM, не отображающий трассировку стека в диалоговом окне, не означает, что ей не хватает этой информации. Убедитесь, что в 'FastMM4Options.inc' определены FullDebugMode, LogMemoryLeakDetailToFile и LogErrorsToFile. Затем найдите файл [ExecutableName] _MemoryManager_EventLog.txt в каталоге исполняемого файла.

В приведенном выше примере FastMM создает следующий файл:

--------------------------------2012/5/27 4:34:46--------------------------------
A memory block has been leaked. The size is: 12

Stack trace of when this block was allocated (return addresses):
40305E 
404B5D 
404AF0 
45C47B 
43D726 
42B0C3 
42B1C1 
43D21E 
76C4702C [GetWindowLongW]
77AE3CC3 [Unknown function at RtlImageNtHeader]

The block is currently used for an object of class: Unknown

The allocation number is: 484

Current memory dump of 256 bytes starting at pointer address 7EF8DEF8:
01 00 00 ...
...

Теперь вы можете запустить приложение, приостановить его и затем найти адреса. Для вышеуказанного журнала и тестового приложения адреса разрешаются:

Stack trace of when this block was allocated (return addresses):
40305E    -> _GetMem
404B5D    -> _NewAnsiString
404AF0    -> _LStrAsg
45C47B    -> TForm1.Button4Click (on FreeMem line)
43D726    -> TControl.Click
... 


изменить: Вместо ручного поиска адресов создайте подробный файл карты через параметры компоновщика, и FastMM сделает это (спасибо комментарию Мейсона).


Ваше редактирование по вопросу отражает довольно похожую утечку, подобную той, что приведена в приведенном выше примере. Если "fList" является регулярным TList, он просто содержит указатели и не знает, на что указывают эти указатели. Следовательно, когда вы удаляете указатель, освобождается только выделенная память для самого указателя, а не поля записи. Таким образом, утечки не имеют ничего общего с константами, переданными в функции, но похожи на шаблон ниже:

var
  assembleditem: PCodeIns;
  p: Pointer;
begin
  new(assembleditem);
  assembleditem^.Caption:='a';
  ..    
  p := assembleditem;
  Dispose(p);

Для записи, которую нужно удалить, код должен указывать указатель на его тип:

Dispose(PCodeIns(p));

Итак, ваш "TCodeInsList.Destroy" должен быть:

destructor TCodeInsList.Destroy;
var
  I: integer;
begin
  for I := 0 to fList.Count - 1 do
    Dispose(PCodeIns(fList[I]));
  fList.Free;
  inherited Destroy;
end;


В конце концов, шаблон, который вы ищете, похоже, ищет места, где код предназначен для бесплатных записей (менее вероятных объектов), имеющих строковые поля. Поиск Dispose, немного менее вероятно FreeMem, еще менее вероятно FreeInstance для освобождения памяти объектов/записей, которые FastMM показывает, поскольку утечка выделенной памяти может помочь.

Ответ 2

Вы правы, что строки должны быть очищены автоматически. Тем не менее, я видел несколько способов вникнуть в это.

Во-первых, если вы делаете вещи со строковой структурой данных напрямую, что может нарушить подсчет ссылок. Это, скорее всего, с количеством строк, которые вы протекаете.

Другой вызов вызывает Halt и оставляет ссылки на строку в стеке. Но вы не собираетесь оставлять 40 000 строковых ссылок в стеке, поэтому я бы искал код, который получает строку, а затем копирует счетчик ссылок.

Ответ 3

С короткими словами строковые типы Delphi подсчитываются по ссылке. Способы выделения и распределения памяти не заботятся об обновлении подсчета ссылок, поэтому компилятор не знает, что строки в ваших записях могут быть фактически освобождены.

Не рекомендуется определять запись со ссылочными типами строк. Раньше у меня была такая же путаница. Если вы посмотрите на источник библиотеки Delphi. Вы найдете много записей, в которых PChar не является строкой.

Некоторые обсуждают записи

Ответ 4

Самый распространенный способ утечки строк - это иметь запись, которая содержит строку и указатель на эту запись. Если вы просто выполните Dispose() для этого указателя, компилятор просто освободит указатель, а не все содержимое в соседней записи. Всегда следите за тем, чтобы ваш код утилизации сообщал компилятору о том, что вы утилизируете.

Например, предположим, что в TTreeView я поместил PMyRecord = ^MyRecord в Node.Data. Если в конце вы зациклите все узлы и просто сделаете Dispose(Node.Data), то любые строки в MyRecord не будут обрабатываться должным образом.

Но если вы избавитесь от своего указателя, объяснив компилятору, что именно является указателем на тип, вызвав Dispose(PMyRecord(Node.Data)), то утечки памяти не будет.

Ответ 5

Я обнаружил, что строка (как поле в записи) может быть утечка даже без операций выделения памяти/указателя.

Звучит безумно, но это правда, по крайней мере, в XE3. Вот пример:

TMyRecord = record
x,
y: integer;
s: ansistring;
end;

function GetMyRec: TMyRecord;
begin
....
end;

....
procedure DoSomething;
var
  rec: TMyRecord;
begin
  ...
  rec := GetMyRec; //First call - everything is OK
  ...
  rec := GetMyRec; //Repeated call > Memory Leak of 
                   //Ansistring !!!!
  //To avoid the leak do the following BEFORE a 
  //repeated call: rec.s := unassigned;
end;