メモリアクセスエラーとメモリリークは、最も注目を集める2つのカテゴリのエラーであるため、その数を防ぐか、少なくとも減らすことに多くの努力が注がれています。 それらの名前は類似性を暗示していますが、いくつかの点で正反対であり、問題の1つを解決することは2番目の問題から私たちを救いません。 マネージ言語の広範な使用は、この考えを裏付けています。メモリを解放する仕事を引き受けることにより、いくつかのメモリアクセスエラーを防ぎます。
簡単に言えば、メモリへのアクセス違反は、不正なデータに対する何らかのアクションであり、メモリリークは、正しいデータに対する特定のアクションの欠如です 。 表形式の場合:
OK OK
最良のプログラムは、OKセルからのみアクションを実行します。正しいデータを操作し、無効なデータを操作しません。 許容可能なプログラムには、正しいが未使用のデータ(メモリリーク)が含まれている場合がありますが、不良なプログラムには不正なデータが使用されます。
言語がRustのように安全なメモリ処理を約束すると、メモリリークが不可能であることを保証しません。
結果
実際のメモリアクセスエラーとリークの最も重要な違いは、潜在的な結果に現れています。前者は非常に深刻な問題につながり、後者は単に迷惑です。
メモリセキュリティは、その他のセキュリティ/正確性の重要な要素です。 プログラムがメモリを間違えた場合、メモリ破損の可能性が排除されないため、その動作について保証することは困難です。 攻撃者は、このようなプログラムのメモリアクセスエラーを利用して、サーバーメモリから機密キーを直接読み取ったり、コンピューターで任意のコードを実行したりできます。
一方、最悪の場合のメモリリークはサービス拒否につながります。メモリを使いすぎると有用なプログラムがクラッシュし、コンピュータが不足して応答しなくなる可能性があります。 この状況は攻撃者によって引き起こされる場合もありますが、これに対処する方法は長い間開発されてきました。 もちろん、サービス拒否は非常に迷惑であり、いくつかの場所では重大な問題がありますが、潜在的なメモリアクセスエラーはそれ以上ではなく、おそらくそれ以上の問題です。 さらに、予想されるメモリアクセスエラーの予測不能性を考えると、同じサービス拒否につながる可能性があります。
その結果、ほとんどのプログラミング言語はメモリリーク(最後の使用後にデータが解放またはクリアされないことを前提とする)に耐えることを好みますが、メモリアクセスエラーには耐えません。 したがって、ほとんどの「安全な」言語は、あなたが意識的に制限を回避することを決定しない限り、それらで書かれたプログラムがそのようなエラーを含まないことを保証します(例えば、Pythonのctypes
モジュールまたはRustのunsafe
キーワードを使用)。 リークについては、(原則として)それらに対処しようとしていますが、保証はしていません。
delete free
メモリアクセスエラーはいくつかの理由で発生しますが、メモリ管理について説明する際には、1つのカテゴリが際立っています。 ウィキペディアを引用するには:
動的メモリの操作エラー-動的メモリとポインタの誤った使用:
- ダングリングポインター-削除されたオブジェクトのアドレスを格納するポインター。
- ダブルリリース-もう一度
free
呼び出すと、同じアドレスにある新しいオブジェクトが途中で削除される場合があります。 アドレスが再利用されていない場合、特に空きリストを使用するアロケーターでは、他の問題が発生する可能性があります。 - 誤ったリリース-誤ったアドレスを
free
関数に渡すと、ヒープが破壊される可能性があります。 - nullポインターにアクセスすると、ほとんどの環境で例外またはプログラムのクラッシュが発生しますが、オペレーティングシステムのカーネルまたはメモリ保護のないシステムまたは大きな負のオフセットを適用するシステムでデータが破損する可能性もあります。
このリストでは、nullポインタへの参照のみが不正な空きメモリによって引き起こされることはありません(メモリを未使用としてマークし、オペレーティングシステムに戻るためにfree
関数を呼び出します)。 したがって、これらの問題はすべて、単にfree
呼び出さないことで回避できます。メモリが解放されない場合、これに関連する問題は発生しません。 上記の表を参照してください:メモリの割り当て解除を削除し、「無効なデータ」列を削除します-すべてのデータは常に正しいです。
もちろん、 free
呼び出しの単純な禁止には欠点があります(メモリの解放に問題がないことに加えて、利点があります:データの有効期間を理解するのに困難はなく、多くの並列アルゴリズムの記述が簡単になります)。 特に、利用可能なすべてのメモリを使い果たさないようにプログラムを書くことが問題になります。 しかし、コンピューターは、人々とは異なり、誤解されていないので、おそらく私たちは彼らの肩にfree
の電話を移すことができます...
リークの最適化
最新のコードの大部分は、Java、Javascript、Python、Rubyなど、メモリを操作する安全性を確保することを目的とした言語で記述されています。 言語ランタイムに組み込まれた「ガベージコレクター」を使用して、メモリをfree
して自動的に管理する明示的な呼び出し(「管理言語」という名前)を行うことなく行います。
実際、ガベージコレクションは、プログラマとプログラムに無限のメモリの幻想を与え、メモリが解放される瞬間を注意深く監視する必要性を取り除く方法です。 サブジェクト領域のロジックに焦点を当てると、ガベージコレクターはメモリの保証されたアイドルセクションを自動的に解放します。 ほとんどすべてのガベージコレクターは、参照されない場合に削除できるデータを控えめに定義します(したがって、ガベージコレクターはすべてのメモリ割り当てを追跡またはアクセスする必要があります)。
ガベージコレクターの高品質の実装は、追加の利点を提供することに注意する価値があります:原則として、メモリ割り当てはオブジェクトの世代が存在する場合の単純なポインターシフトとして実装され、移動するガベージコレクターの機能はキャッシュの局所性を向上させます(これは、データアクセスとこれは、ほとんどのマネージ言語のポインターによって発生します)。 ただし、これらの機能は記事のトピックには関係ありません。
実際には、プログラマはメモリが無限ではないという事実を考える必要はほとんどありませんが、メモリを操作するセキュリティは、必要に応じて提供されます。 高性能コードでは、ガベージコレクションの非効率性を回避するために、あらゆる種類のトリック(たとえば、頻繁にオブジェクトを作成および削除する際のメモリ割り当てを回避するオブジェクトプールなど)に頼らなければならないことがよくあります。 また、 リンクを忘れたためにデータが必要以上に長く存続する可能性もあります。
それでも、実際に発生する問題を考慮に入れても、目標は達成されます。無料通話がないことで、(いくつかの)メモリの問題がないことが保証されます。
抽象化が少ない
自動メモリ管理に代わる方法に言及するしかありません。「不正なデータ」列を完全に取り除くのではなく、メモリの操作にセキュリティ上の問題がないことを保証することしかできません。 Rustプログラミング言語はまさにそれを行います。
この方法により、手動でリソースを解放する必要がなくなりますが、 drop
機能を使用すると、事前にデストラクタを呼び出すことができます。 CおよびC ++とは異なり、Rustはコンパイル段階で、不正確になったそのようなデータのさらなる使用を禁止し、エラーを防ぎます。
ただし、このモデルはリークがないことを保証するものではありません。Rust(および同様の原理を持つ言語)の改訂された表には、まだ「メモリリーク」セルがあります。
OK OK
多くの人は、「メモリリーク」と「メモリの安全性」の違いを認識していません。 Rustはメモリを扱うセキュリティを保証していると聞いて、彼らは単にリーク保護を期待しており、この言語が低レベルプログラミングの分野ではできないようなことを理解できない。 Rustはメモリアクセスエラーを許可しませんが、リークを除外しません。
std::mem::forget
そして最後に、Rustにはデータを解放済みとしてマークし、それ以上アクセスできforget
ようにするforget
関数がありますが、デストラクタを呼び出さないため、メモリリークが発生する可能性があります。 長い間、この関数は安全でないとマークされていました。つまり、Rustは、メモリリークがメモリを扱う安全性と同様に、プログラマが意識的に選択する必要があることを暗黙的に暗示していました。 ただし、実際には、参照カウントポインターまたはデッドロックによりリークが発生する可能性があります。 その結果、メモリアクセスエラーの防止に重点を置いて、他のすべての言語と同様にリークに対抗するためにあらゆる努力をしましたが、 forget
機能は安全になりました。
現代のC ++と同様に、Rustはかなり良い仕事をします:RAIIベースのリソース管理、つまりデストラクタは、特にRustで使用されるデフォルトの移動セマンティクスと組み合わせて、メモリを管理するための強力なツールです 。 次の2つの理由で漏れが保証されていません。
- メモリリークの正式な定義を提供するのは簡単ではありません(少なくともそのような定義の有用性はコンテキストに依存します)。
- 比較的まれな境界ケースがありますが、明らかにオーバーヘッドなしでは静的に防止できません。
Rust標準ライブラリは、リークが安全であることを期待していますが、誤動作につながる可能性があります。 言い換えると、データが解放されない場合、望ましくない動作が発生する可能性がありますが、結果はセグメンテーション障害やメモリ破損よりも損傷が少ないです。