LoginSignup
5
8

More than 3 years have passed since last update.

【Delphi】String 型変数で身に覚えのないメモリリークが報告される

Last updated at Posted at 2020-08-28

はじめに

Delphi ではプロジェクトファイルの先頭で次のように記述しておくと、アプリケーション終了時に (していれば) メモリリークが報告されます 1

ReportMemoryLeaksOnShutdown := True;

そこそこ大きなプログラムを書くと、何故か String 型変数でメモリリークが報告される事があります。こういう奴です。

Unexpected Memory Leak
An unexpected memory leak has occurred. The unexpected small block leaks are:

nn - nn bytes: UnicodeString x 1

See also:

原因

例えば次のようなコードがあったとします。

SimpleRec1.dpr
program SimpleRec1;

{$APPTYPE CONSOLE}

type
  TUserPassRec = record
    User: Integer;
    Pass: String;
  end;
  PUserPassRec = ^TUserPassRec;

var
  PUP: PUserPassRec;
begin
  ReportMemoryLeaksOnShutdown := True;
  New(PUP);
  try
    PUP^.User := 123;
    PUP^.Pass := 'HogeHoge';
    Writeln(PUP^.User, PUP^.Pass);
  finally
    DisPose(PUP);
  end;
end.

このコードはメモリリークを起こしませんが、例えば C / C++ 由来のコードを移植したとしましょう。「ヌル終端文字列は扱いづらいので String にした」とかそういうシチュエーションです。メモリ確保/解放関数に別のものが使われているかもしれません。

SimpleRec2.dpr
program SimpleRec2;

{$APPTYPE CONSOLE}

type
  TUserPassRec = record
    User: Integer;
    Pass: String;
  end;
  PUserPassRec = ^TUserPassRec;

var
  PUP: PUserPassRec;
begin
  ReportMemoryLeaksOnShutdown := True;
  PUP := GetMemory(SizeOf(TUserPassRec));
  try
    PUP^.User := 123;
    PUP^.Pass := 'HogeHoge';
    Writeln(PUP^.User, PUP^.Pass);
  finally
    FreeMemory(PUP);
  end;
end.

このコード例ではレコード型動的変数のメモリが正しく解放されているにも関わらず、メモリリークが報告されると思います。何故かと言うと、文字列型の変数を未割当て状態にせずにメモリ解放したからです。

説明になっていないかもしれませんが、Delphi の String 型変数は特定の条件下では 未割当て状態にしてからメモリを解放 する必要があります。その条件とは次の 2 つを満たす場合です。

  1. Dispose() 以外の方法でメモリを解放した場合。
  2. String 型変数の中身が空ではない場合 (未割当て状態でない場合)。

この条件を満たす場合には、メモリ解放前に Finalize() を使って String 型変数を未割当て状態にしなければなりません。

SimpleRec3.dpr
program SimpleRec3;

{$APPTYPE CONSOLE}

type
  TUserPassRec = record
    User: Integer;
    Pass: String;
  end;
  PUserPassRec = ^TUserPassRec;

var
  PUP: PUserPassRec;
begin
  ReportMemoryLeaksOnShutdown := True;
  PUP := GetMemory(SizeOf(TUserPassRec));
  try
    PUP^.User := 123;
    PUP^.Pass := 'HogeHoge';
    Writeln(PUP^.User, PUP^.Pass);
    Finalize(PUP^.Pass); // <- 未割当て状態にする
  finally
    FreeMemory(PUP);
  end;
end.

レコード型に String 型のフィールドを配置すると、この状況が発生しうるのです。

また、String 型フィールドを未割当て状態にするのに、Finalize() の代わりに SetLength() を使う事もできます。

    SetLength(PUP^.Pass, 0); // <- 長さを 0 にする (未割当て状態にする)

See also:

少し複雑な例

上記例では問題解決方法を説明するために、String 型のフィールドを未割当状態にしていました。少し複雑な例を見てみましょう。

SimpleRec4.dpr
program SimpleRec4;

{$APPTYPE CONSOLE}

type
  TUserPassRec = record
    User: Integer;
    Pass: String;
  end;

  TEmployeeRec = record
    Code: Integer;
    FirstName: String;
    LastName: String;
    UserPass: TUserPassRec;
  end;
  PEmployeeRec = ^TEmployeeRec;

var
  PE: PEmployeeRec;
begin
  ReportMemoryLeaksOnShutdown := True;
  PE := GetMemory(SizeOf(TEmployeeRec));
  try
    PE^.Code := 1;
    PE^.FirstName := 'John';
    PE^.LastName  := 'Smith';
    PE^.UserPass.User := 123;
    PE^.UserPass.Pass := 'HogeHoge';
    Writeln(PE^.UserPass.User, PE^.UserPass.Pass);
    Finalize(PE^.FirstName);     // <- 未割当て状態にする
    Finalize(PE^.LastName );     // <- 未割当て状態にする
    Finalize(PE^.UserPass.Pass); // <- 未割当て状態にする
  finally
    FreeMemory(PE);
  end;
end.

コード例のように String 型のフィールドを一つずつ未割当て状態にしてもいいのですが、Finalize() にレコード型の動的変数を指定すれば String 型のフィールドすべてを一気に未割当て状態にする事ができます。

...
    Writeln(PE^.UserPass.User, PE^.UserPass.Pass);
    Finalize(PE^); // <- 未割当て状態にする (動的変数指定)
  finally
  ...

但し、この時 Finalize() にレコード型ポインタ変数を渡してはいけません。次の例は誤りです。

...
    Writeln(PE^.UserPass.User, PE^.UserPass.Pass);
    Finalize(PE); // <- × 未割当て状態にする (ポインタ変数指定)
  finally
  ...

おわりに

レコード型を動的変数として使う場合には、由緒正しく New()Dispose() を使いましょう、というお話でした。

See also:


  1. 素の状態で使えるのは Delphi 2006 (BDS 2006 / Turbo Delphi) から。 

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8