Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What is going on with this article?
@ht_deko

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

はじめに

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) から。 

6
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ht_deko
とある熊本の障害復旧(トラブルシューター)

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
6
Help us understand the problem. What is going on with this article?