stackoverflowで、彼らはC ++の冗長性に関する最近の投稿で共鳴する質問をしました。 C#開発者のEric Lippertによって拡張された回答が与えられました。
質問:私は次のコードを持っています:
int * foo() { int a = 5; return &a; } int main() { int* p = foo(); cout << *p; *p = 8; cout << *p; }
例外なく問題なく動作します
5 8
これはどのように可能ですか? ローカル変数メモリは関数の外部からアクセスできますか?
答えは:ホテルの部屋を借ります。 ナイトスタンドの一番上の引き出しに本を置いて、寝ます。 翌朝、ホテルを出ますが、鍵を渡すのを忘れます。 あなたは鍵を盗んだ!
1週間後、ホテルに戻り、チェックインせずに、盗まれた鍵で古い部屋にひそかに忍び寄り、箱を開けます。 あなたの本はまだそこにあります。 信じられない!
これはどのように可能ですか? ナイトスタンドの内容は部屋を借りずに利用できますか?さて、このシナリオが問題なく実際に発生することは明らかです。 ホテルを出るとすぐに本を削除するような神秘的な力はありません。 盗まれた鍵で部屋に入ることを妨げるような神秘的な力はありません。
ホテルは本
をきれいにする
必要はありません。 あなたは彼女との契約に署名しませんでした。 盗まれた鍵を使って部屋を不法に手に入れる場合、ホテルの警備員はあなた
を捕まえる
必要はありません。 あなたは彼らと契約を結んでいませんでした。「私の部屋に入ろうとしたら、私を止めなければなりません。」 しかし、あなたは彼らと契約を結び、「退院後に私の部屋に入らないことを約束します。」 そして、
あなたはこの契約
を破りました。
この状況では、何でも起こり得ます。 運がよければ本はあるかもしれません。 他の誰かの本がそこにあり、あなたの本がホテルの暖炉にあるかもしれません。 誰かがあなたが入った瞬間にそこに座って、あなたの本を切り裂きます。 ホテルはナイトスタンドを取り外し、そこに戸棚を置くことができました。 ホテル自体は取り壊され、サッカースタジアムに置き換わる可能性があり、周囲を登ると爆発で死亡します。
何が起こるかわからない-ホテルからチェックアウトし、後で違法に使用できるように鍵を盗んだとき、システムの規則
を破る
ことにしたので、予測可能で安全な世界に住む権利を放棄した。
C ++は安全な言語ではありません 。 彼は喜んでルールを破らせます。 アクセスが拒否されている部屋に入る、そこにいないかもしれないボックスに登るなど、違法で愚かなことをしようとしても、C ++はあなたを止めません。 より安全な言語は、機能を減らすことでこの問題を解決します。 たとえば、より厳密なキー制御。
更新するああ、この答えは多くの注目を集めています。 もう少し技術的なポイントを与えるのが適切だと思いました。
コンパイラーは、このコードによって操作されるデータのストレージを制御するコードの生成に関与しています。 メモリ管理用のコードを生成するにはさまざまな方法がありますが、長年にわたって2つの簡単な方法が定着しました。
1つ目は、各バイトの「寿命」を簡単に予測できない、ある種の「長命の」場所を持つことです。 コンパイラは、「ヒープマネージャ」への呼び出しを作成します。「ヒープマネージャ」は、必要に応じてストレージスペースを動的に割り当て、必要でないときに解放する方法を認識しています。
2番目の方法は、各バイトのライフタイムがよく知られている、ある種の「短命」な場所を持つことです。具体的には、そのような場所のライフタイムはネストパターンに従います。 つまり、寿命の短い変数の最長寿命のメモリ割り当ては、寿命の短い変数のメモリ割り当てと明らかに重複しています。
ローカル変数は2番目の方法に従います。 メソッドの実行が開始されると、ローカル変数が有効になります。 メソッドが別のメソッドを呼び出すと、それらのローカル変数が有効になります。 それらは、最初のメソッドの変数が死ぬ前に死にます。 変数を保存する場所の寿命の開始と終了の相対的な順序は、事前に計算できます。
この理由から、スタックにはFILOプロパティ(先着順、最後の脱出権)があるため、通常ローカル変数はスタック上に作成されます。
まるでホテルが部屋を順番に借りることを決めたかのようで、自分の部屋よりも大きい部屋の部屋にいるすべてのゲストがチェックアウトされるまでチェックアウトできません。
それでは、スタックについて考えてみましょう。 多くのオペレーティングシステムでは、プロセスごとに1つのスタックを取得し、このスタックのサイズは固定されています。 メソッドを呼び出すと、何かがスタックにプッシュされます。 質問の作成者がここで行ったように、メソッドからスタックにポインターを渡すと、これはメモリ内のどこかのポインターにすぎません。 たとえば、ホテルからチェックアウトするときは、最も番号の大きい部屋からチェックアウトします。 誰もそこに定住せず、あなたが違法にそこに着いた場合、あなたの物は
この特定のホテルのその場
で保証され
ます 。
スタックは非常に安価でシンプルなため、一時的なストレージに使用します。 C ++実装は、スタックを使用してローカル変数を保存する必要はありません-束を使用できます。 しかし、彼女はプログラムを遅くするので、それをしません。
C ++の実装は、スタック上のゴミに手を触れないで、後で不正に戻ることができます。 コンパイラーは、解放した「部屋」のすべてをリセットするコードを生成することは絶対に合法です。 繰り返しますが、彼はそうしません、なぜならそれは高価な手術だからです。
C ++実装は、スタックが圧縮されたときに古いアドレスが正しいメモリページを指すことを保証するために必要ではありません。 彼女はOSに「このページはこれで完了です。 特に断りのない限り、誰かがそれに触れるとプロセスを強制終了する例外をスローします。」 繰り返しますが、実装は低速でオプションなので、これを行いません。
代わりに、実装により、結果なしでミスを犯すことができます。 ほとんどの場合。 ある日まで、何かが本当にうまくいかず、プロセスが爆発します。
これには問題があります。 多くのルールがあり、それらを誤って破ることは非常に簡単です。 これを繰り返しました。 さらに悪いことに、これらの問題は、メモリが数十億ナノ秒にわたって破損したときに表面化し、犯人を見つけることはすでに非常に困難です。
よりメモリセーフな言語は、機能を制限することでこの問題を解決します。 「通常の」C#では、ローカル変数のアドレスを取得してどこかに戻すか、後で使用するために保存することは単に不可能です。 変数のアドレスを取得できますが、言語は非常に巧妙に設計されているため、変数の死後にこのアドレスを使用することはできません。 アドレスを取得してメソッドから返すには、コンパイラを特別な「安全でない」モードにし、プログラムに「安全でない」という単語
を含めて、危険なことをしていて規則に違反する可能性があることを思い出させる必要があります。