« DelphiでiFilterを使用する | トップページ | MiBarcode ver7.1フルパッケージ版 »

2017.05.13

Delphiでのメモリリーク

MiGrep2でふとメモリリークを起こしていたりしないよね?ということで念のため確認してみました。
メモリリークが置きているかどうかを確認するのは簡単で、プロジェクトソースに一行追加するだけです(Delphi2006以降)。

program MemLeakTestProg;

uses
Vcl.Forms,
MemLeakTest in 'MemLeakTest.pas' {TestForm};

{$R *.res}

begin
ReportMemoryLeaksOnShutdown := True; //これを追加
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TTestForm, TestForm);
Application.Run;
end.

で結果は・・・思いっきりメモリリークを起こしていました(^^;
原因を色々と調べたところ、TListview.item.dataポインタに格納している検索ファイルの付属情報を格納している構造体ポインタ内のstringが開放されていないことがわかりました。
ファイル情報を登録する際に、

var
finfo: PFoundInfo; // 検索情報構造体ポインタ
begin
New(finfo);
finfo^.Fullpathname := ほにゃらら
fino^.xxxx
Listview.item[x].Data := finfo;

として、開放する際に何も考えずに
procedure TMainForm.ListView1Deletion(Sender: TObject; Item: TListItem);
begin
if Assigned(Item.Data) then
Dispose(Item.Data);

としてしまっていました。
これだと、Item.Dataに格納されたポインタ構造体は解放されるものの、構造体メンバは開放されないのでした。
ここはItem.dataポインタを格納されている構造体でキャストしてやることで正しく下放されるのでした。
Dispose(PFoundInfo(Item.Data));

以下はテスト用プログラムです。

unit MemLeakTest;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;

type
// テスト用構造体1(メンバにStringを含む)
PRec1 = ^TRec1;
TRec1 = record
TestString1,
TestString2: string;
end;
// テスト用構造体1(メンバもポインタ)
PRec2 = ^TRec2;
TRec2 = record
TestString1,
TestString2: PString;
end;
TTestForm = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;

var
TestForm: TTestForm;

implementation

{$R *.dfm}

// 直接操作
procedure TTestForm.Button1Click(Sender: TObject);
var
r1: PRec1;
i: integer;
begin
for i := 1 to 100 do
begin
New(r1);
r1^.TestString1 := 'TEST1';
r1^.TestString2 := 'TEST2';
Dispose(r1); // メモリリークは起こらない
end;
end;

// 代入ポインタ操作1
procedure TTestForm.Button3Click(Sender: TObject);
var
r1: PRec1;
i: integer;
p: Pointer;
begin
for i := 1 to 100 do
begin
New(r1);
p := r1; // ポインタpにメモリを確保したr1を代入
PRec1(p)^.TestString1 := 'TEST1';
PRec1(p)^.TestString2 := 'TEST2';
Dispose(p); // pを開放→TestString1/TestString2が開放されずメモリリーク
end;
end;

// 代入ポインタ操作2
procedure TTestForm.Button2Click(Sender: TObject);
var
r1: PRec1;
i: integer;
p: Pointer;
begin
for i := 1 to 100 do
begin
New(r1);
p := r1; // ポインタpにメモリを確保したr1を代入
PRec1(p)^.TestString1 := 'TEST1';
PRec1(p)^.TestString2 := 'TEST2';
Dispose(PRec1(p)); // pをPRec1でキャストして開放→メモリリークは起こらない
end;
end;

// 構造体メンバもポインタ
procedure TTestForm.Button4Click(Sender: TObject);
var
r2: PRec2;
i: integer;
p: Pointer;
begin
for i := 1 to 100 do
begin
New(r2);
p := r2;
New(PRec2(p)^.TestString1);
New(PRec2(p)^.TestString2);
PRec2(p)^.TestString1^ := 'TEST1';
PRec2(p)^.TestString2^ := 'TEST2';
Dispose(PRec2(p)^.TestString1); // TestStrng1, TextString2を開放
Dispose(PRec2(p)^.TestString2);
Dispose(p); // ポインタpを開放→メモリリークは起こらない
end;
end;

end.

で、今プログラムをもしやと思ってLazarus/FPCでも実行してみましたが結果は同じでした・・・まぁそうですよね。

最終的にMiGrep2ではメモリ効率も考えて、上の四番目の方法(メンバもポインタ)に変更しました。ちょっと面倒ですが、全部自前で問答を見たほうが後々問題が起きにくいかなと思った次第です。

|

« DelphiでiFilterを使用する | トップページ | MiBarcode ver7.1フルパッケージ版 »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/22406/65272334

この記事へのトラックバック一覧です: Delphiでのメモリリーク:

« DelphiでiFilterを使用する | トップページ | MiBarcode ver7.1フルパッケージ版 »