TOPページへ戻る  PCページへ戻る  一覧へ戻る

New()と.Creatとメモリの解放

Delphiのヘルプを見ると、New()で確保したメモリはDispose()で、.Creatで 作ったオブジェクトは.Freeで解放することと書いてあります。また、Freeメソッドの ヘルプには

Free メソッドを呼び出すと,オブジェクトを廃棄できます。 Free メソッドは,オブジェクトの参照が nil ではない場合, 自動的にデストラクタを呼び出します。実行時にインスタンス化されたオブジェクトは, オーナーを持っていない場合,Free メソッドを呼び出して廃棄しなければなりません。 結果として,オブジェクトが適切に処理され,メモリが解放されます。
と書かれています。「Freeメソッドでオブジェクトが廃棄されるなら、TList.Itemsも 破棄される」でしょうか?これをちょっと確かめるため、次のコードを実行してみました。 このプログラムは、フォーム上にボタンが1個配置された単純なものです。 Items[ i ]をDisposeせずにいきなりList.Freeを実行しています。


unit Unit1;

interface

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls;

type
   TForm1 = class(TForm)
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
   private
     { Private 宣言 }
   public
     { Public 宣言 }
   end;

var
   Form1: TForm1;

implementation

 {$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var List: TList;
    i	: Integer;
    pIn : PInteger;
begin

List := TList.Create;

i := 0;
while i < 65535 do
    begin
    New(pIn);
    i := i + 1;
    pIn^ := i;
    List.Add(pIn);
    end;
// (A)
List.Free;
end;
end.

このプログラムを実行してボタンを数回押した場合、メモリ使用量が 3692kB(ボタン押す前)→4472kB(1回)→5240kB(2回)→6012kB(3回)と増えて いきます。要するにNew()で確保してItemsプロパティに代入した場合でも、 FreeメソッドはItemsプロパティを解放しないということです。

では、New()手続きで確保したメモリをDispose()手続きで廃棄するよう、次の コードを上の(A)部に挿入します。


i := 0;
while i < List.Count do
    begin
    if Assigned(List[i]) then Dispose(List[i]);	// Assigned()でList[i]が
    i := i + 1;					//    nilでないことを確認 
    end;

先ほどと同様にこのプログラムを実行すると、メモリ使用量は 3696kB(ボタン押す前)→3708kB(1回)→3708kB(2回)→3708kB(3回)と メモリが増え続ける現象が出なくなります。

さて、これで十分でしょうか?ポインタ変数pInもNew(pIn)手続きを しているのでDispose(pIn)手続きが必要じゃないでしょうか?これを確かめるため、 次のプログラムを実行してみましょう。このプログラムは、 数値を配列として使いたい - - リストの場合で使用したプログラムに、 Dispose(pIn)を加えたものです。確実に廃棄できるようfinally節に入れています。


 unit Unit1;

interface

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls;

type
   TForm1 = class(TForm)
     Label1: TLabel;
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
   private
     { Private 宣言 }
   public
     { Public 宣言 }
   end;

var
   Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var List: TList;
    i,Answer : Integer;
    pIn: PInteger;
begin
List := TList.Create;

i := 0;
try
while i < 65535 do
    begin
    try
    New(pIn);
    i := i + 1;
    pIn^ := i;
    List.Add(pIn);
    except
      on E:EOutOfMemory do
        begin
	ShowMessage('メモリ不足です');
	exit;
      end;
     end;
    end;
i := 0;
Answer := 0;
while i < 65535 do
    begin
    Answer := Answer + Integer(List.Items[i]^);
    i := i + 1;
    end;
Label1.Caption := IntToStr(Answer);

finally
    begin
    Dispose(pIn);		// ←追加したコード
    i := 0;
    while i < List.Count do
        begin
        if Assigned(List.Items[i]) then Dispose(List.Items[i]);
        i := i + 1;
        end;
    List.Free;
    end;
  end;
end;

end.

結果は”無効なポインタ操作”エラーが発生します。理由は次の通りです。

New(pIn)で確保されたメモリは、List[ i ]に代入されていきます。 そのためwhile文が終了した時点ではpIn = List[65534]となっています。 Dispose(pIn)を行うと、List[65534]が持つメモリ領域を開放してしまいます。 その後にDispose(List[ i ])でList[ i ]が全て破棄されますが、List[65534]は既に 破棄されているため無効なポインタ操作となってしまうのです。

ようするに、代入したポインタ変数を削除しては いけないのです。(List[65534]はDispose(List[i])で解放されますし、pIn変数は Button1Click手続きが終了すればプログラムが破棄します)

TOPページへ戻る  PCページへ戻る  一覧へ戻る