メモリ(およびリソースロック)は、プログラムの実行中に決定論的なポイントでOSに返されます。プログラムの制御フローは、それ自体で、特定のリソースを確実に割り当て解除できる場所を知るのに十分です。人間のプログラマーが、プログラムが終了したときにfclose(file)
を書き込む場所を知っているように。
GCは、制御フローが実行される実行時にそれを直接把握することにより、これを解決します。しかし、制御フローに関する真の真の情報源は情報源です。したがって、理論的には、ソース(またはAST)を分析することにより、コンパイル前にfree()
呼び出しを挿入する場所を決定できるはずです。
参照カウントはこれを実装する明白な方法ですが、ポインターがまだ参照されている(まだスコープ内にある)が、不要になった状況に遭遇するのは簡単です。これは、手動でポインターの割り当てを解除する責任を、それらのポインターへのスコープ/参照を手動で管理する責任に変換するだけです。
プログラムのソースを読み取ることができるプログラムを作成して、次のことができるように思われます。
これをすでに行っているものはありますか? RustまたはC++スマートポインター/ RAIIは同じことだとは思いません。
次の(不自然な)例を見てみましょう。
void* resource1;
void* resource2;
while(true){
int input = getInputFromUser();
switch(input){
case 1: resource1 = malloc(500); break;
case 2: resource2 = resource1; break;
case 3: useResource(resource1); useResource(resource2); break;
}
}
Freeはいつ呼び出されますか? mallocの前にresource1
に割り当てることができません。これはresource2
にコピーされる可能性があるためできません。resource2
に割り当てる前に、ユーザーが介入1。
確実にする唯一の方法は、resource1とresource2をテストして、ケース1と2で等しくないかどうかを確認し、等しくない場合は古い値を解放することです。これは基本的に参照カウントであり、参照が2つしかないことがわかっています。
RAIIは自動的に同じものではありませんが、同じ効果があります。 「これにアクセスできなくなったとき、どうやって知るのですか?」という質問に対する簡単な答えを提供します。 scopeを使用して、特定のリソースが使用されているときに領域をカバーします。
同様の問題を検討することをお勧めします。「プログラムが実行時に型エラーを受けないようにするにはどうすればよいですか?」これに対する解決策はnotプログラムを通じてすべての実行パスを予測することですが、型注釈と推論のシステムを使用して、そのようなエラーが発生しないことを証明します。 Rustは、この証明プロパティをメモリ割り当てに拡張する試みです。
プログラムを制約するために何らかの注釈を使用する場合にのみ、停止の問題を解決する必要なく、プログラムの動作に関する証明を書くことができます。セキュリティ証明(sel4など)も参照してください。
はい、これは実際に存在しています。 MLキット は、記述された戦略(多かれ少なかれ)を利用可能なメモリ管理オプションの1つとして備えた生産品質のコンパイラーです。また、従来のGCを使用したり、参照カウントとハイブリッド化したりすることもできます(ヒーププロファイラーを使用して、プログラムで実際に最良の結果が得られる戦略を確認できます)。
領域ベースのメモリ管理に関する回顧 は、ML Kitの元の作者による記事で、成功と失敗を説明しています。最終的な結論は、ヒーププロファイラの支援を得て書き込む場合、この戦略は実用的であるということです。
(これは、実用的なエンジニアリングの質問への答えとして、通常は停止問題に目を向けるべきではない理由の良い例です。一般的なケースを解決したくない必要としないほとんどの現実的なプログラム。)
プログラムの制御フローのすべての順列を予測する
ここに問題があります。順列の量は、重要なプログラムにとっては非常に膨大(実際には無限です)であり、必要な時間とメモリにより、これは完全に非実用的です。
停止の問題は、これがすべての場合に可能ではないことを証明します。ただし、それは非常に多くの場合に依然として可能であり、実際、ほとんどすべてのコンパイラーがおそらく大多数の変数に対して実行します。これにより、コンパイラは、長期のヒープストレージではなく、スタックまたはレジスタに変数を割り当てるだけで安全であると判断できます。
純粋な関数または本当に優れた所有権セマンティクスがある場合は、静的分析をさらに拡張できますが、コードが取る分岐が増えるほど、法外なコストがかかります。
1人のプログラマーまたはチームがプログラム全体を作成する場合、メモリー(およびその他のリソース)を解放する必要がある設計ポイントを特定できるのは合理的です。したがって、はい、より限定されたコンテキストでは、設計の静的分析で十分な場合があります。
ただし、サードパーティのDLL、API、フレームワーク(およびスレッドも投入)を考慮に入れると、使用しているプログラマーが、どのエンティティーがどのメモリーを所有しているかを正しく推論することは非常に難しい(つまり、すべての場合で不可能)それが最後に使用されたとき。私たちのいつもの言語の疑いは、オブジェクトと配列のメモリ所有権の移転を浅くて深いもので十分に文書化していません。プログラマーがそれを(静的または動的に!)推論できない場合、コンパイラーもその可能性はほとんどありません。繰り返しになりますが、これはメモリの所有権の転送がメソッド呼び出しやインターフェイスなどでキャプチャされないためです。そのため、コードをいつどこでメモリを解放するかを静的に予測することはできません。
これは非常に深刻な問題であるため、最近の多くの言語ではガベージコレクションを選択しています。ガベージコレクションは、最後のライブ参照の後でメモリを自動的に再利用します。ただし、GCのパフォーマンスコストは非常に高くなります(特にリアルタイムアプリケーションの場合)ので、万能ではありません。さらに、GCを使用してもメモリリークが発生する可能性があります(たとえば、増大するだけのコレクション)。それでも、これはほとんどのプログラミング演習に適したソリューションです。
いくつかの選択肢があります(一部は新興)。
Rust言語はRAIIを極端に使用します。これは、クラスおよびインターフェースのメソッドの所有権の転送をより詳細に定義する言語構造を提供します。たとえば、オブジェクトが転送先と借用先の間で呼び出し元と呼び出し先、またはより長寿命のオブジェクト。メモリ管理に対してコンパイル時の安全性が高いレベルを提供します。しかし、それは取るに足らない言語ではなく、問題がないわけでもありません(たとえば、私は考えていません)設計は完全に安定しており、特定のものがまだ実験中であり、したがって変化しています。
SwiftとObjective-Cはさらに別の方法で、ほとんどが自動参照カウントです。参照カウントは循環の問題に入り込みます。たとえば、特にクロージャーでは、プログラマーにとって大きな課題があります。
プログラムが不明な入力に依存していない場合は、可能です。これは可能です(複雑なタスクであり、時間がかかる場合がありますが、プログラムについても同様です)。このようなプログラムは、コンパイル時に完全に解決可能です。 C++の用語では、それらは(ほぼ)constexpr
sで完全に構成される可能性があります。簡単な例は、piの最初の100桁を計算するか、既知の辞書をソートすることです。
一般に、メモリの解放は停止の問題と同等です。プログラムが(静的に)停止するかどうかを静的に判断できない場合、メモリを(静的に)解放するかどうかも判断できません。
function foo(int a) {
void *p = malloc(1);
... do something which may, or may not, halt ...
free(p);
}
https://en.wikipedia.org/wiki/Halting_problem
とはいえ、Rustはとてもいいことです... https://doc.Rust-lang.org/book/ownership.html