Я пишу на Delphi, и раньше у меня было много проблем из-за багов с утечкой памяти (когда я в коде создал объект или динамический массив, а потом забыл его освободить). Из-за этой проблемы утилита “Set point group” в моей программе иногда вылетала с ошибкой Out of memory. Теперь я решил эту проблему так: написал объект “сборщик мусора”, который запоминает все создаваемые программой классы и динамические массивы, и в конце работы подсчитывает, какие из них не были уничтожены, и выводит отчёт в текстовом файле. Выглядит код примерно так:
После этого я сделал все классы своей программы наследниками TSafeObject (вместо TObject), и старые баги стали легко обнаруживаться. Объект ProjectGarbageFinder пишет репорт в текстовой файл, из которого обычно сразу понятно где был баг.type
TSafeObject=class
public
constructor Create;
destructor Destroy; override;
procedure SetThisObjectAsDebugging;//Не будет выводиться сообщение об ошибке
end;
TGarbageFinder=class
private
FObjectsCount:integer;
FObjectsCapacity:integer;
FObjects:array of record
obj:tobject;
CreationTime:tdatetime;
CreationTimeTicks:longword;
Tag:integer;
PosInFullLog:integer;
FirstNum:integer;//Под каким номером был этот объект в самом начале
RandTag:integer;
Debugging:boolean;//Для таких не надо выдавать сообщение об ошибке
end;
FMemoryBlocks:array of record
MemMos:pointer;
BytesCount:integer;
CreationTime:tdatetime;
CreationTimeTicks:longword;
end;
FMemoryBlocksCount:integer;
FMemoryBlocksCapacity:integer;
FLogMessages:tstringlist;
FFullLogCount:integer;
FFullLogCapacity:integer;
FFullLog:array of record
LogMesType:integer;
PostTimeTicks:cardinal;
PostTime:tdatetime;
end;
FObjectsSaved:tlist;
FSaveRepFileName:tfilename;
FReport:tstringlist;
FSectionStarted:boolean;
FSectionsCount:integer;
FTwiceDestroyedObjectsCount:integer;
FTotalObjectsNotDestroyedCount:integer;
FTotalMemoryBlocksNotDestroyedCount:integer;
FTotalTwiceDestroyedObjectsCount:integer;
procedure Grow;
procedure GrowFullLog;
procedure GrowMemBlocksArray;
procedure DeleteObject(objnum:integer);
procedure DeleteMemoryBlock(blocknum:integer);
procedure AddObject(obj:tobject; crtime:tdatetime; crtimeticks:longword);
procedure AddMemoryBlock(pos:pointer; bytescount:integer);
function AddMessageToLogMessagesList(str:string):integer;//Возвращает индекс этой строки в FLogMessages;
procedure AddItemToFullLog(messtypenum:integer);
procedure SetObjectsCapacity(newcapacity:integer);
procedure SetFullLogCapacity(newcapacity:integer);
procedure SetMemoryBlocksCapacity(newcapacity:integer);
function NotDebuggingObjectsCount:integer;//Количество объектов за исключением debugging
public
FReportFileTooBig:boolean;
function SomeGarbageRemaining:boolean;
function FindObject(obj:tobject; direction:tdirection):integer;//-1 если нет
function FindMemoryBlock(pos:pointer; direction:tdirection):integer;
procedure SetObjectTag(obj:tsafeobject; newtag:integer);
procedure AddStringToFullLog(str:string);
procedure StartSection;
procedure FinishSection;
procedure AddToReport(str:string);
procedure ObjectCreated(obj:tobject); overload;
//procedure ObjectCreated(obj:tobject; logstr:string); overload;
procedure ObjectDestroyed(obj:tobject);
procedure MemoryBlockCreated(pos:pointer; bytescount:integer);
procedure MemoryBlockDestroyed(pos:pointer);
procedure PlaceTag(tag:integer);
constructor Create(repfilename:tfilename);
procedure FinalizeReport;
procedure WriteReportToFile;
destructor Destroy; override;
end;
var
ProjectGarbageFinder:TGarbageFinder;
…
constructor TSafeObject.Create;
begin
inherited;
if poGarbageFinderActive then begin
ProjecTGarbageFinder.ObjectCreated(self);
end;
end;
destructor TSafeObject.Destroy;
begin
if poGarbageFinderActive then begin
ProjecTGarbageFinder.ObjectDestroyed(self);
end;
inherited;
end;
Должен сказать, что такой подход у меня во многом от моего непрофессионализма – если бы я раньше знал про переменную ReportMemoryLeaksOnShutdown в Delphi, мне бы изначально, возможно, всё это писать не потребовалось бы. Хотя я думаю, что всё равно мой сборщик мусора полезен как дополнение к этой опции.
Как я понимаю, для C++ можно написать такую же штуку, а для более "продвинутых" языков вроде C# она не нужна, так как в них есть свой собственный сборщик мусора (но за это приходится платить скоростью работы программы).