コンピュータサイエンスにおいて、手動メモリ管理とは、プログラマが手動命令を使用して未使用のオブジェクト、つまりガベージを識別して解放することを指します。1990年代半ばまでは、業界で使用されるプログラミング言語の大部分が手動メモリ管理をサポートしていましたが、ガベージコレクションは1959年にLispで導入されて以来存在していました。[ 1 ]しかし今日では、 Javaなどのガベージコレクションを備えた言語がますます普及しており、 Objective-CやSwiftなどの言語は自動参照カウントによって同様の機能を提供しています。現在でも広く使用されている主な手動管理言語はCとC++です。Cの動的メモリ割り当てを参照してください。
多くのプログラミング言語では、空き領域から新しいオブジェクトを割り当てるmallocタイミングを決定するために手動の手法が用いられます。C では関数が、C++ と Java ではnew演算子が使用され、その他多くの言語 (Python など) ではすべてのオブジェクトが空き領域から割り当てられます。オブジェクトを作成するタイミング (オブジェクトの作成) の決定は一般的に簡単で問題ありませんが、オブジェクト プールなどの手法では、オブジェクトがすぐに使用される前に作成されることがあります。本当の課題はオブジェクトの破棄です。オブジェクトが不要になったタイミング (つまり、ガベージになったタイミング) の決定と、その基となるストレージを再利用のために空き領域に戻す手配です。手動メモリ割り当てでは、これもプログラマが手動で指定します。Cfree()の関数やC++ の演算子などを使用します。これは、自動変数に保持されているオブジェクト、特に関数の (非静的)ローカル変数deleteの自動破棄とは対照的です。これらの変数は、C と C++ ではスコープの終了時に破棄されます。
例えば:
手動メモリ管理は、誤って使用されると、メモリ安全性の違反やメモリリークなど、プログラムにいくつかの主要な種類のバグを引き起こすことが知られています。これらはセキュリティバグの重要な原因です。[ 2 ]
ガベージコレクションのみを使用する言語は、最後の2種類の欠陥を回避できることが知られています。メモリリークは依然として発生する可能性があり(世代別ガベージコレクションや保守的なガベージコレクションでは、限定的なリークが頻繁に発生します)、一般的には手動システムにおけるメモリリークよりも深刻度は低くなります。
手動メモリ管理には、リソース取得と初期化(RAII)パラダイムによる自動リソース管理を可能にするという、正確性に関する利点が1つあります。
これは、オブジェクトが希少なシステムリソース(グラフィックリソース、ファイルハンドル、データベース接続など)を所有し、オブジェクトが破棄される際にこれらのリソースを解放する必要がある場合、つまりリソース所有権の存続期間がオブジェクトの存続期間と連動する必要がある場合に発生します。手動管理機能を持つ言語では、オブジェクトの初期化時(コンストラクタ内)にリソースを取得し、オブジェクトの破棄時(デストラクタ内)にリソースを解放することで、この問題を解決できます。このデストラクタは、正確なタイミングでリソースを取得します。これは、リソース取得が初期化である(RAII)と呼ばれます。
これは決定論的参照カウントでも使用できます。C++ では、この機能は、本来手動で行われるフレームワーク内でメモリ解放を自動化するためにさらに活用され、言語の標準ライブラリのスマートポインタを使用してメモリ管理を実行するのが一般的なパラダイムです。Rustのように厳密な所有権を強制する言語では、RAII がリソース管理のデフォルトの動作になります。[ 3 ]
このアプローチは、ファイナライゼーションが非決定論的であり、場合によってはまったく発生しないため、ほとんどのガベージコレクション言語(特にトレースガベージコレクタやより高度な参照カウント)では使用できません。つまり、ファイナライザメソッドがいつ呼び出されるか、または呼び出されるかどうかを定義する(または決定する)ことが困難です。これは一般にファイナライザ問題として知られています。ガベージコレクタを実装する Java やその他の言語では、メモリ以外の希少なシステムリソースをdispose パターンで手動で管理することがよくあります。リソースを管理するオブジェクトはdispose()、そのようなリソースを解放し、オブジェクトを非アクティブとしてマークするメソッドを実装することが期待されます。プログラマは、dispose()希少なグラフィックス リソースの「リーク」を防ぐために、必要に応じて手動で呼び出すことが期待されます。スタック リソース(単一のコード ブロック内で取得および解放されるリソース)については、Python のwith、C# のusing-with-resources(オブジェクトのメソッドを呼び出すDispose())、Java の-with-resources( を実装し、そのメソッドを呼び出すtry任意のオブジェクトで使用可能)などのさまざまな言語構造によって自動化できます。java.lang.AutoCloseableclose()
deferメカニズムとは、コードの実行を後回しにできる機能であり、通常はスコープの終了時または関数の終了時まで延期します。これは、他の言語におけるfinallyブロックに似ています。
C言語( C29以降)では、あるdeferメカニズムが導入されています。defer実行中に遭遇した各ブロックは、スコープの終了時(遭遇時ではなく)に、遭遇した順序とは逆の順序で実行されます。[ 4 ]
#include <stddefer.h> #include <stdio.h> #include <stdlib.h>int processFile ( const char path []) { FILE * f = fopen ( path , "r" ); if ( ! f ) { return -1 ; }defer { printf ( "ファイルを閉じます。\n " ); fclose ( f ); };char * buffer = malloc ( 1024 ); if ( ! buffer ) { return -2 ; }defer { printf ( "バッファを解放します。\n " ); free ( buffer ); };// 複数の早期終了をシミュレートするif ( fgets ( buffer , 1024 , f )) { return -3 ; // 両方の defer がまだ実行される}if ( buffer [ 0 ] == '#' ) { return -4 ; // 両方のdeferがまだ実行される}printf ( "処理中: %s \n " , buffer ); return 0 ; }deferC++は、RAIIがこのユースケースをカバーしているため、Cを統合するかどうかについてまだコメントしていません。
Go言語では、defer関数呼び出しを周囲の関数が戻り値を返したときに実行するようにスケジュールするために使用されます。複数のdefer関数呼び出しは、後入れ先出し(LIFO)の順序で実行されます。
パッケージメインimport "fmt"func main ( ) { fmt.Println ( " start" )defer fmt.Println ( "最初のdefer" ) defer fmt.Println ( " 2番目のdefer " )fmt.Println ( " end " ) }Zigでは、deferブロックレベルで実行され、関数の実行時だけでなく、現在のスコープが終了したときに実行されます。Goとは異なり、Zigではdeferオーバーヘッドが発生しません。
const std = @import ( "std" );pub fn main () void { std . debug . print ( "start \n " , .{});{ defer std.debug.print ( "inner defer \ n " , .{}); std.debug.print ( " inside block \ n " , . { } ) ; }std.debug.print ( "end \n " , " .{ } ) ; }Zigにはerrdefer、関数がエラーで終了した場合にのみ実行される機能も含まれています。
手動メモリ管理の支持者の多くは、ガベージコレクションなどの自動的な手法と比較して、手動管理の方が優れたパフォーマンスを発揮すると主張している。従来はレイテンシが最大の利点だったが、もはやそうではない。手動割り当ては、多くの場合、参照の局所性において優れている。
メモリが貴重なリソースであるシステムでは、手動割り当ての方が解放が速いため、より適していることが知られています。メモリシステムは、プログラムのワーキングセットのサイズが使用可能なメモリのサイズに近づくと、頻繁に「スラッシング」を起こすことがあります。ガベージコレクションシステムでは、未使用のオブジェクトはすぐに解放されないため、手動管理システムよりも長く未解放の状態にとどまり、実効ワーキングセットのサイズが大きくなります。
手動管理には、多くの実証済みのパフォーマンス上の欠点がある。
delete実行されるたびにオーバーヘッドが発生しますが、このオーバーヘッドはガベージコレクションサイクルで償却できます。これは、削除呼び出しを同期する必要があるマルチスレッドアプリケーションにおいて特に当てはまります。レイテンシは議論の的となる点であり、時代とともに変化してきた。初期のガベージコレクタや単純な実装では、手動によるメモリ管理と比較してパフォーマンスが非常に悪かったが、洗練された現代のガベージコレクタは、手動によるメモリ管理と同等かそれ以上のパフォーマンスを発揮することが多い。
手動割り当てでは、単純なストップ・ザ・ワールド方式のガベージコレクションで発生するような長い「一時停止」時間は発生しません。ただし、最新のガベージコレクターには、多くの場合目立たない収集サイクルがあります。
手動メモリ管理とガベージコレクションはどちらも、潜在的に無制限の解放時間という問題を抱えています。手動メモリ管理では、単一のオブジェクトの解放にそのメンバの解放が必要となり、さらにそのメンバのメンバの解放なども再帰的に必要となる場合があるため、この問題が生じます。一方、ガベージコレクションでは、長い収集サイクルが発生する可能性があります。これは、無制限の収集サイクルが一般的に許容されないリアルタイムシステムにおいて特に問題となります。リアルタイムガベージコレクションは、ガベージコレクタを一時停止することで実現できますが、リアルタイム手動メモリ管理では、大規模な解放を回避するか、解放を手動で一時停止する必要があります。