原則として、任意のアドレスに対して読み取りと書き込みを行うことができますが、整理された適切に定義された方法でデータにアクセスすることだけが安全で意味があります。
メモリ割り当て (明示的または暗黙的) の目的は、混沌に秩序をもたらすことです。配列を宣言するbuf
と、メモリの小さなブロックがスタックに予約されます。
通常、割り当てには特定のアライメントがあります (場合によっては特定の最小サイズ、またオペレーティング システムは非常に粗いレベルでしか間違ったアクセスを検出できません)。そのため、割り当てられたメモリ ブロックと小さな領域の間に小さなギャップが生じることがよくあります。 「何か悪いこと」が起こっていないように見えますが、そうではないふりをする必要があり、これらの実装の詳細を有利に利用することを考えるべきではありません。
コード例が「機能する」のは、不運にも未割り当てまたは書き込み保護されたメモリ ページにアクセスできず、アプリケーションのクラッシュの原因となる別の重要なスタック値 (関数のリターン アドレスなど) を上書きしなかったためです。
私はわざと「ラッキー」ではなく「アンラッキー」と言っています。これは正しくないコード2であり、そのようなコードは早期にクラッシュするため、問題を検出して修正できます。そうしないと、まったく関係のない時間や場所で発生したように見える問題を診断するのが非常に難しくなる可能性があります。今は動作しても、明日 (または、別のコンピューター、別のコンパイラ、またはわずかに異なるコードで) 動作するという保証はまったくありません。
メモリの割り当ては、通常、3 段階のプロセスです。これは、C ライブラリによって行われるオペレーティング システムへの割り当て要求 (通常は、要求に直接対応するものではありません) であり、その後にライブラリ内で記録が行われ、あなたによって約束されます。オペレーティング システム レベルでは、ページ レベルでの実際の物理割り当ては、最初にメモリにアクセスするときにオンデマンドで発生します。これは、C ライブラリが以前にアクセスされた場所の割り当てを要求したと仮定した場合です。
スタック割り当ての場合、実際には 1 つの特殊レジスタをデクリメントするだけで済むため、ライブラリ レベルでのプロセスはいくぶん簡単になりますが、これはほとんどの場合関係ありません。コンセプトはそのままです。
あなたが約束したことは、同意した領域からのみ読み取りまたは書き込みを行うということであり、これはあなたにとって最も重要なことです。
約束を (故意に、または偶然に) 破っても、まだ「機能する」ことがありますが、それはまったくの偶然です。
スタック上では、遅かれ早かれ、いくつかのローカル変数のストア (レジスタにキャッシュされている場合は検出されない可能性があります) を上書きし、最後に戻りアドレスを上書きします。これにより、ほぼ確実にクラッシュ (または同様の望ましくない動作) が発生します。関数が戻るとき。ヒープでは、他のプログラム データを上書きしたり、予約済みとしてオペレーティング システムに伝達されていないページにアクセスしたりすることができます。その場合、プログラムは直ちに終了します。
1仮想メモリとページ保護については、一瞬考えないようにしましょう。
2厳密に言えば、
不正なコードではなく、未定義の動作を呼び出すコードです。ただし、未割り当てのメモリを上書きすることは、私の意見では、「正しくない」というラベルに値するほど深刻です。