Cのような言語では、プログラマはfreeへの呼び出しを挿入することが期待されています。コンパイラがこれを自動的に行わないのはなぜですか?人間は(バグを無視して)妥当な時間内にそれを行うので、不可能ではありません。
EDIT:今後の参考のために、ここで興味深い例があり、別の議論です。
Cのような言語では、プログラマはfreeへの呼び出しを挿入することが期待されています。コンパイラがこれを自動的に行わないのはなぜですか?人間は(バグを無視して)妥当な時間内にそれを行うので、不可能ではありません。
EDIT:今後の参考のために、ここで興味深い例があり、別の議論です。
回答:
プログラムがメモリを再び使用するかどうかは決定できないためです。これはfree()
、すべての場合にいつ呼び出すべきかをアルゴリズムが正しく判断できないことを意味します。つまり、これを試みたコンパイラーは、メモリリークのあるプログラムや解放されたメモリを引き続き使用するプログラムを生成することを意味します。コンパイラーが2番目のコンパイラーを実行しないことを保証し、プログラマーがfree()
これらのバグを修正するために呼び出しを挿入free()
できるようにしたとしても、そのコンパイラーをいつ呼び出すかを知るfree()
ことは、試みなかったコンパイラーを使用するときにいつ呼び出すかを知ることよりも困難です助けるために。
free()
正しく挿入することを期待しています。
デイビッド・リチャービーが正しく指摘したように、この問題は一般的に決定不能です。オブジェクトの活性はプログラムのグローバルプロパティであり、一般にプログラムへの入力に依存します。
正確な動的 ガベージコレクションでさえ、決定できない問題です!すべての実際のガベージコレクターは、割り当てられたオブジェクトが将来必要になるかどうかの控えめな近似として到達可能性を使用します。これは良い近似ですが、それでも近似です。
しかし、それは一般的に真実です。コンピューターサイエンスビジネスで最も悪名高いコップアウトの1つは、「一般に不可能であるため、何もできない」ことです。それどころか、前進することが可能な多くの場合があります。
参照カウントに基づく実装は、「コンパイラが割り当て解除を挿入する」に非常に近いため、違いを見分けることは困難です。LLVMの自動参照カウント(Objective-CおよびSwiftで使用)は有名な例です。
領域の推論とコンパイル時のガベージコレクションは、現在活発な研究分野です。MLやMercuryなどの宣言型言語では、オブジェクトの作成後にオブジェクトを変更することはできません。
現在、人間のトピックに関して、人間が割り当ての有効期間を手動で管理する主な方法は3つあります。
割り当て解除ステートメントの最適な配置が決定できないことは事実ですが、それは単にここでの問題ではありません。人間にとってもコンパイラにとっても決定的ではないため、それが手動プロセスであるか自動プロセスであるかにかかわらず、最適な割り当て解除配置を常に意識的に選択することは不可能です。そして、完璧な人はいないので、十分に高度なコンパイラーは、ほぼ最適な配置を推測する際に人間よりも優れているはずです。そのため、明示的な割り当て解除ステートメントが必要な理由は、決定不能です。
外部の知識が割り当て解除ステートメントの配置を通知する場合があります。これらのステートメントを削除することは、操作ロジックの一部を削除することと同等であり、コンパイラーにそのロジックを自動生成するように依頼することは、考えていることを推測することと同等です。
たとえば、Read-Evaluate-Print-Loop(REPL)を書いているとします。ユーザーがコマンドを入力すると、プログラムがそれを実行します。ユーザーは、REPLにコマンドを入力することにより、メモリを割り当て/割り当て解除できます。ソースコードは、ユーザーがコマンドを入力したときの割り当て解除を含め、可能な各ユーザーコマンドに対してREPLが行うべきことを指定します。
しかし、Cソースコードが割り当て解除のための明示的なコマンドを提供しない場合、コンパイラは、ユーザーがREPLに適切なコマンドを入力したときに割り当て解除を実行する必要があると推測する必要があります。そのコマンドは「割り当て解除」、「無料」、または他の何かですか?コンパイラーには、コマンドの内容を知る方法がありません。そのコマンドワードを探すためにロジックでプログラムし、REPLがそれを見つけたとしても、ソースコードで明示的に指示しない限り、コンパイラは割り当て解除で応答する必要があることを知る方法がありません。
tl; dr 問題は、Cソースコードがコンパイラに外部知識を提供しないことです。プロセスが手動であるか自動化されているかにかかわらず、決定不能は問題ではありません。
現在、投稿された回答はどれも完全に正しいものではありません。
コンパイラーが割り当て解除を自動的に挿入しないのはなぜですか?
ある人はそうします。(後で説明します。)
当然、free()
プログラムが終了する直前に呼び出すことができます。しかし、あなたの質問にはfree()
、できるだけ早く電話をかける必要があることが暗示されています。
free()
メモリに到達できなくなったらすぐにCプログラムを呼び出すという問題は決定できません。つまり、有限時間内に答えを提供するアルゴリズムでは、カバーされない場合があります。これ-および任意プログラムの他の多くの決定不能性-は停止問題から証明できます。
決定できない問題は、コンパイラであれ人間であれ、アルゴリズムによって常に有限時間で解決できるとは限りません。
人間は(自分自身で)アルゴリズムによってメモリの正確性を検証できる Cプログラムのサブセットを書き込もうとします。
一部の言語では、コンパイラに#5を組み込むことで#1を実現しています。メモリ割り当てを任意に使用するプログラムではなく、それらの決定可能なサブセットを許可します。FothとRustは、Cよりもメモリの割り当てが制限されている言語の2つの例でありmalloc()
、(1)決定可能セットの外側にプログラムが記述されているかどうかを検出できます(2)割り当て解除を自動的に挿入します。
「人間がやるので、不可能ではない」というのはよく知られている誤りです。私たちが作成するものを必ずしも理解しているわけではありません(制御は言うまでもありません)。お金は一般的な例です。特に人的要因が存在しないと思われる場合、技術的な問題で成功する可能性を過大評価する(時には劇的に)傾向があります。
コンピュータープログラミングでの人間のパフォーマンスは非常に低く、コンピューターサイエンスの研究(多くの専門教育プログラムを欠いている)は、この問題に単純な修正がない理由を理解するのに役立ちます。いつか、おそらくそれほど遠くないところで、仕事の人工知能に取って代わられるかもしれません。それでも、常に自動的に割り当て解除を行う一般的なアルゴリズムはありません。
自動メモリ管理の欠如は、言語の機能です。
Cは、ソフトウェアを簡単に作成するためのツールではありません。これは、コンピューターに指示どおりに実行させるためのツールです。これには、選択した時点でのメモリの割り当てと割り当て解除が含まれます。Cは、コンピューターを正確に制御したい場合、または言語/標準ライブラリの設計者が期待したものとは異なる方法で物事を行いたい場合に使用する低レベル言語です。
問題は主に歴史的な人工物であり、実装不可能ではありません。
ほとんどのCコンパイラがコードをビルドする方法は、コンパイラが一度に各ソースファイルのみを見るようにすることです。プログラム全体を一度に見ることはありません。あるソースファイルが別のソースファイルまたはライブラリから関数を呼び出すと、コンパイラは、関数の実際のコードではなく、関数の戻り値の型を持つヘッダーファイルのみを認識します。これは、ポインターを返す関数がある場合、ポインターが指しているメモリーを解放する必要があるかどうかをコンパイラーが判断する方法がないことを意味します。それを決定するための情報は、その時点ではコンパイラーに表示されません。一方、人間のプログラマは、関数のソースコードやドキュメントを自由に検索して、ポインタで何をする必要があるかを知ることができます。
C ++ 11やRustのような最新の低レベル言語を調べると、ポインターのタイプでメモリの所有権を明示的にすることで、ほとんどの問題が解決されていることがわかります。C ++ではunique_ptr<T>
、プレーンの代わりにを使用T*
してメモリを保持しunique_ptr<T>
、プレーンとは異なり、オブジェクトがスコープの最後に到達するとメモリが解放されるようにしますT*
。プログラマはメモリunique_ptr<T>
を別のメモリに渡すことができますが、メモリをunique_ptr<T>
指すのは1人だけです。そのため、誰がメモリを所有し、いつ解放する必要があるかは常に明確です。
C ++は、後方互換性の理由から、古いスタイルの手動メモリ管理を許可しているため、バグの作成やの保護を回避する方法が可能unique_ptr<T>
です。Rustは、コンパイラエラーを介してメモリ所有権ルールを適用するという点で、さらに厳密です。
決定不能性、停止問題などについては、はい、Cのセマンティクスに固執すると、すべてのプログラムでメモリを解放する必要があるかどうかを判断することはできません。ただし、アカデミックな演習やバグのあるソフトウェアではなく、ほとんどの実際のプログラムでは、いつ解放するか、いつ解放しないかを決定することは絶対に可能です。そもそも、人間がいつ解放すべきかを判断できる唯一の理由です。
他の答えは、ガベージコレクションを実行できるかどうか、実行方法の詳細、および問題のいくつかに焦点を当てています。
まだカバーされていない問題の1つは、ガベージコレクションの避けられない遅延です。Cでは、プログラマがfree()を呼び出すと、そのメモリはすぐに再利用できます。(少なくとも理論的には!)プログラマーは100MB構造を解放し、ミリ秒後に別の100MB構造を割り当て、全体のメモリ使用量が同じままになることを期待できます。
これは、ガベージコレクションには当てはまりません。ガベージコレクションされたシステムでは、未使用のメモリをヒープに戻すのに多少の遅延があります。100MB構造が範囲外になり、1ミリ秒後にプログラムが別の100MB構造をセットアップした場合、システムが短時間200MBを使用していると合理的に予想できます。その「短い期間」は、システムに応じてミリ秒または秒になる場合がありますが、まだ遅延があります。
RAMと仮想メモリのギグを備えたPCで実行している場合、もちろんこれに気付かないでしょう。ただし、リソースが限られているシステム(組み込みシステムや電話など)で実行している場合は、これを真剣に検討する必要があります。これは単なる理論的なものではありません。WinCEシステムで.NET Compact Frameworkを使用してC#で開発しているときに、これが(デバイスの種類の問題をクラッシュさせるなどの)問題を引き起こすことを個人的に見てきました。
この問題は、割り当て解除がプログラマがソースコードの他の部分から推測するものであると仮定しています。そうではありません。「プログラムのこの時点で、メモリー参照FOOはもう役に立たない」とは、割り当て解除ステートメントに(手続き言語で)エンコードされるまでプログラマーの心にしかわからない情報です。
理論的には他のコード行と違いはありません。コンパイラが「プログラムのこの時点で、レジスタBARの入力を確認する」または「関数呼び出しがゼロ以外を返した場合、現在のサブルーチンを終了する」を自動的に挿入しないのはなぜですか?コンパイラの観点から、この答えに示されているように、理由は「不完全」です。しかし、プログラマーが自分の知っていることをすべて伝えなかった場合、どのプログラムも不完全性に悩まされます。
実際には、割り当て解除は単調な作業または定型的なものです。私たちの脳は自動的にそれらを埋め、それについて不平を言い、「コンパイラーはそれと同じかそれ以上にできる」という感情は真実です。しかし、理論的にはそうではありませんが、幸いなことに他の言語はより多くの理論の選択肢を与えてくれます。
何をされ、ガベージコレクションがあり、かつ参照カウント(のObjective-C、スウィフト)を使用して、コンパイラがあります行って。参照カウントを行うものは、強力な参照サイクルを回避することにより、プログラマーの助けが必要です。
本当の答えそのコンパイラの作家は十分に良いと、コンパイラでは、それを使用可能にするのに十分な速さで道を考え出したていないです「なぜ」。コンパイラライターは通常非常に賢いので、十分に適切で高速な方法を見つけるのは非常に難しいと結論付けることができます。
それが非常に、非常に難しい理由の1つは、もちろんそれが決定できないことです。コンピュータサイエンスでは、「決定可能性」について話すとき、「正しい決定を下す」ことを意味します。もちろん、人間のプログラマーは、正しい決定に限定されないため、メモリーの割り振り解除の場所を簡単に決定できます。そして、彼らはしばしば間違った決定を下します。
Cのような言語では、プログラマはfreeへの呼び出しを挿入することが期待されています。コンパイラがこれを自動的に行わないのはなぜですか?
それでおしまい。これはCの設計です。コンパイラは、メモリブロックを割り当てる意図が何であるかを知ることができません。人間はそれを行うことができます。なぜなら、彼らはすべてのメモリブロックの目的を知っているからです。それは、書かれているプログラムの設計の一部です。
Cは低レベル言語であるため、メモリのブロックを別のプロセスまたは別のプロセッサに渡すことは非常に頻繁です。極端な場合、プログラマは意図的にメモリのチャンクを割り当て、システムの他の部分にメモリのプレッシャーをかけるためだけに再び使用することはありません。コンパイラには、ブロックがまだ必要かどうかを知る方法がありません。
Cのような言語では、プログラマはfreeへの呼び出しを挿入することが期待されています。コンパイラがこれを自動的に行わないのはなぜですか?
Cや他の多くの言語では、コンパイル時に実行すべきことが明らかな場合に、コンパイラにこれと同等の機能を実行させる機能があります。自動期間変数(通常のローカル変数)の使用。コンパイラーは、そのような変数に十分なスペースを配置し、それらの(明確に定義された)ライフタイムが終了したときにそのスペースを解放する責任があります。
C99以降、可変長配列はCの機能であるため、自動期間オブジェクトは、原則として、計算可能な期間の動的に割り当てられたオブジェクトが行うCのほぼすべての機能を提供します。もちろん、実際には、C 実装はVLAの使用に大幅な実用上の制限を課す可能性があります(つまり、スタックに割り当てられた結果としてサイズが制限される場合があります)が、これは言語設計の考慮事項ではなく、実装の考慮事項です。
意図された使用法で自動期間を与えることができないオブジェクトは、正確には、コンパイル時に寿命を決定できないオブジェクトです。