Приложение 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;