技術的には、オペレーティング システムはアクセス時に任意のメモリ ページを割り当てることができますが、割り当てることができない、またはできない重要な理由があります。
メモリ領域が異なれば、目的も異なります。
- コード。読み取りと実行はできますが、書き込みはできません。
- リテラル (文字列、const 配列)。このメモリは読み取り専用であり、読み取り専用である必要があります。
- ヒープ。読み取りと書き込みはできますが、実行はできません。
- スレッドスタック。2 つのスレッドが互いのスタックにアクセスする理由はないため、OS はそれを禁止することもできます。さらに、トレッドスタックは、トレッドが終了したときに割り当てを解除できます。
- メモリ マップト ファイル。この領域への変更は、特定のファイルに影響するはずです。ファイルが読み取り用に開かれている場合、同じメモリ ページは読み取り専用であるため、プロセス間で共有される可能性があります。
- カーネル空間。通常、アプリケーションはその領域にアクセスすべきではありません (またはアクセスできません)。カーネル コードのみがアクセスできます。これは基本的にカーネルのスクラッチ スペースであり、プロセス間で共有されます。ネットワーク バッファはそこに存在する可能性があるため、パケットがいつ到着しても、常に書き込みに使用できます。
- ...
OS は、認識されていないすべてのメモリ アクセスが、より多くのヒープ スペースを割り当てようとする試みであると想定する場合がありますが、次のようになります。
- アプリケーションがユーザー コードからカーネル メモリにアクセスした場合は、強制終了する必要があります。32 ビット Windows では、
1<<31
上位 (上位ビット セット) または3<<30
上位 (上位 2 ビット セット) のすべてのメモリがカーネル メモリです。未割り当てのメモリ領域がユーザー空間にあると想定しないでください。
- アプリケーションがメモリ領域の使用を考えているが OS に通知しない場合、OS はそのメモリに別のものを割り当てる可能性があります (OS: 確かに、あなたのファイルは
0x12341234
; アプリ: しかし、そこにデータを保存したかったのです)。配列の最後に触れることでOSに伝えることができますが(とにかく信頼できません)、OS関数を呼び出す方が簡単です。関数呼び出しが「から始まる 10MB のヒープを与えてください」ではなく、「10MB のヒープを与えてください」であることは良い考えです。0x12345678
- アプリケーションがそれを使用してメモリを割り当てる場合、通常はまったく割り当て解除されません。OS はまだ未使用のページを保持する必要があるため、これは問題になる可能性があります (ただし、Java 仮想マシンも割り当てを解除しないので、ちょっと)。
プログラムの実行が異なると、変数のアドレスが異なります
これはメモリ レイアウトのランダム化と呼ばれ、適切なアクセス許可 (スタック スペースは実行可能ではありません) と共に使用され、バッファ オーバーフロー攻撃をより困難にします。アプリを強制終了することはできますが、任意のコードを実行することはできません。
メモリ割り当てに関するいくつかのリンク (例: ヒープ内)。
アロケーターが使用するアルゴリズムは何ですか? 最も簡単なアルゴリズムは、常に最も早い利用可能な位置に割り当て、各メモリ ブロックから次のメモリ ブロックにリンクし、空きブロックまたは使用済みブロックの場合はフラグを格納することです。より高度なアルゴリズムでは、常に 2 の累乗または固定サイズの倍数のサイズでブロックを割り当てて、メモリの断片化 (多数の小さな空きブロック) を防ぐか、別の構造でブロックをリンクして、十分なサイズの空きブロックをより速く見つけます。
さらに簡単な方法は、割り当てを解除せず、最初の (そして唯一の) 空きブロックを指し、そのサイズを保持することです。残りのスペースが小さすぎる場合は、それを捨てて、OS に新しいものを要求してください。
メモリ アロケータについて魔法のようなことは何もありません。彼らがすることは次のことだけです。
- OS に大きなリージョンを要求し、
- それをより小さなチャンクに分割します
- それなし
- あまりにも多くのスペースを無駄にしたり、
- 時間がかかりすぎます。
とにかく、メモリ割り当てに関するウィキペディアの記事はhttp://en.wikipedia.org/wiki/Memory_managementです。
興味深いアルゴリズムの 1 つに、「(バイナリ) バディ ブロック」と呼ばれるものがあります。2 のべき乗サイズのプールをいくつか保持し、それらを再帰的に小さな領域に分割します。各リージョンは、完全に割り当てられているか、完全に解放されているか、完全に解放されていない 2 つのリージョン (バディ) に分割されます。分割されている場合、このブロック内の最大の空きブロックのサイズを保持するには 1 バイトで十分です。