# 【Delphi】String 型変数で身に覚えのないメモリリークが報告される --- tags: Delphi programming Pascal embarcadero objectpascal created_at: 2020-08-28 updated_at: 2020-08-29 --- # はじめに Delphi ではプロジェクトファイルの先頭で次のように記述しておくと、アプリケーション終了時に (していれば) メモリリークが報告されます [^1]。 ```pascal 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:** - [System.ReportMemoryLeaksOnShutdown (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.ReportMemoryLeaksOnShutdown) # 原因 例えば次のようなコードがあったとします。 ```pascal: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** にした」とかそういうシチュエーションです。メモリ確保/解放関数に別のものが使われているかもしれません。 ```pascal: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** 型変数を未割当て状態にしなければなりません。 ```pascal: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()` を使う事もできます。 ```pascal SetLength(PUP^.Pass, 0); // <- 長さを 0 にする (未割当て状態にする) ``` **See also:** - [System.Initialize (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Initialize) - [System.Finalize (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Finalize) - [System.SetLength (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SetLength) - [長い文字列型 - 内部データ形式(Delphi)(Qiita)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%86%85%E9%83%A8%E3%83%87%E3%83%BC%E3%82%BF%E5%BD%A2%E5%BC%8F%EF%BC%88Delphi%EF%BC%89#.E9.95.B7.E3.81.84.E6.96.87.E5.AD.97.E5.88.97.E5.9E.8B) - [10.2. New と Dispose - <10> ポインタ型 (標準 Pascal 範囲内での Delphi 入門) (Qiita)](./745ad4a792d277d33831.md#102-new-%E3%81%A8-dispose) ## 少し複雑な例 上記例では問題解決方法を説明するために、**String** 型のフィールドを未割当状態にしていました。少し複雑な例を見てみましょう。 ```pascal: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** 型のフィールドすべてを一気に未割当て状態にする事ができます。 ```pascal ... Writeln(PE^.UserPass.User, PE^.UserPass.Pass); Finalize(PE^); // <- 未割当て状態にする (動的変数指定) finally ... ``` 但し、この時 `Finalize()` にレコード型ポインタ変数を渡してはいけません。次の例は誤りです。 ```pascal ... Writeln(PE^.UserPass.User, PE^.UserPass.Pass); Finalize(PE); // <- × 未割当て状態にする (ポインタ変数指定) finally ... ``` # おわりに 「レコード型を動的変数として使う場合には、由緒正しく `New()` と `Dispose()` を使いましょう」というお話でした。 **See also:** - [メモリと文字列バッファ確保の関数 (Mr.XRAY)](http://mrxray.on.coocan.jp/Delphi/Others/MemAlloc.htm) - [【Delphi】構造体と文字列とポインタ (Qiita)](./87aee1c7aee75aba90a2.md) [^1]: デフォルト環境で使えるのは Delphi 2006 (BDS 2006 / Turbo Delphi) から。