1. ホーム
  2. c++

[解決済み】ローカル変数のメモリはスコープ外からアクセスできる?

2022-03-23 15:05:50

質問

次のようなコードがあります。

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

そして、コードは実行時の例外もなく、そのまま実行されています!

出力されたのは 58

どうしてそうなるのでしょうか?ローカル変数のメモリは、その関数の外ではアクセスできないのでは?

どうすれば解決するの?

<ブロッククオート

どうしてそうなるのでしょうか?ローカル変数のメモリは、その関数の外ではアクセスできないのでは?

あなたはホテルの部屋を借りた。ベッドサイドテーブルの一番上の引き出しに本を入れて寝ます。 翌朝、チェックアウトをするが、鍵を返すのを忘れる。あなたは鍵を盗んでしまったのです。

一週間後、あなたはホテルに戻り、チェックインせずに、盗んだ鍵で元の部屋に忍び込み、引き出しの中を探します。本がまだそこにある。驚きました。

どうしてそうなるんだ?ホテルの部屋の引き出しの中身は、部屋を借りていなければアクセスできないのではないのか?

まあ、明らかにそのシナリオは現実の世界でも問題なく起こりうることです。その部屋にいる許可が下りると、本が消えてしまうような不思議な力は存在しないのです。また、盗まれた鍵で部屋に入れないようにする不思議な力もない。

ホテルの経営者は 必須 を削除してください。物を置いていったらシュレッダーにかけてくれるという契約はしていないはずです。盗んだ鍵を取り返そうと違法に部屋に入り直した場合、ホテルのセキュリティスタッフは 必須 忍び込むのを捕まえるため。あなたは彼らと、「もし私が後で部屋に忍び込もうとしたら、あなたは私を止めなければなりません」という契約をしたわけではありません。むしろ、あなたは彼らと、「私は後で自分の部屋に忍び込まないことを約束します」という契約を交わしたのです。 あなたが壊した .

このような状況において 何が起こるかわからない . 本がそこにある可能性がある -- あなたは幸運でした。他の人の本がそこにあって、あなたの本がホテルの炉の中にあることもあり得る。あなたが入ってきたときに誰かがいて、あなたの本をバラバラにしてしまうかもしれない。ホテルがテーブルと本を完全に取り除いて、洋服ダンスに取り替えているかもしれない。ホテル全体が取り壊され、サッカースタジアムに変わるところで、あなたがこそこそしている間に爆発して死んでしまうかもしれないのです。

何が起こるかわからない。ホテルをチェックアウトし、後で違法に使用するために鍵を盗んだとき、あなたは予測可能で安全な世界で生きる権利を放棄したのだから。 あなた システムのルールを破ることを選択したのです。

C++は安全な言語ではありません . システムのルールを破ることを快く許してくれるのです。もしあなたが、許可されていない部屋に戻って、もうそこにないかもしれない机をあさるような、違法で愚かなことをしようとしても、C++はあなたを止めません。C++よりも安全な言語は、例えば鍵の管理をより厳しくするなど、あなたの力を制限することでこの問題を解決しています。

アップデイト

なんと、この回答は注目を浴びていますね。(なぜかは分かりませんが、私はただの楽しい例えに過ぎないと思っていました。)とにかく。

私は、もう少し技術的な考えをもって、これを少し更新することが重要かもしれないと思いました。

コンパイラは、プログラムによって操作されるデータの保存を管理するコードを生成するビジネスである。メモリを管理するコードを生成する方法にはさまざまなものがありますが、時を経て、2つの基本的な手法が定着してきました。

1つ目は、ある種のquot;long lived"ストレージ領域を持つことで、ストレージ内の各バイトの寿命、つまりあるプログラム変数と有効に関連付けられる期間を事前に容易に予測することができません。コンパイラは、ストレージを必要なときに動的に割り当て、不要になったときに回収する方法を知っているヒープマネージャの呼び出しを生成します。

2つ目の方法は、各バイトの寿命がよく分かっている「短命」な記憶領域を持つ方法である。ここでは、寿命は「入れ子」パターンに従っています。短寿命の変数のうち最も寿命の長いものが、他のどの短寿命の変数よりも先に割り当てられ、最後に解放されます。短寿命の変数は長寿命の変数の後に割り当てられ、その前に解放されます。これらの短寿命の変数の寿命は、長寿命の変数の寿命の中に「入れ子」になっています。

ローカル変数は後者のパターンで、あるメソッドが入力されると、そのローカル変数が生き返ります。そのメソッドが別のメソッドを呼び出すと、新しいメソッドのローカル変数が生き返ります。最初のメソッドのローカル変数が死ぬ前に、新しいメソッドのローカル変数が死にます。 ローカル変数に関連するストレージの寿命の始まりと終わりの相対的な順序は、前もって調べておくことができます。

このため、ローカル変数は通常、quot;stack"データ構造上のストレージとして生成されます。スタックは、最初にプッシュされたものが、最後にポップオフされるという特性を持っているからです。

ホテルが部屋を順番にしか貸さないことにして、自分より部屋番号の高い人が全員チェックアウトするまでチェックアウトできないようなものです。

では、スタックについて考えてみましょう。多くのオペレーティングシステムでは、1つのスレッドに1つのスタックがあり、スタックは一定の固定サイズになるように割り当てられています。メソッドを呼び出すと、スタックにものが押し出されます。元の投稿者のように、スタックへのポインタをメソッドから渡すと、それは100万バイトのメモリブロックの中央へのポインタに過ぎません。この例では、あなたがホテルをチェックアウトしたとき、一番番号の大きい部屋からチェックアウトしたことになります。 もし、あなたの後に誰もチェックインせず、あなたが不法に部屋に戻ったとしても、あなたの荷物はすべてそこにあることが保証されています。 このホテルでは .

一時的な保存にスタックを使うのは、本当に安くて簡単だからです。C++の実装では、ローカルの保存にスタックを使う必要はなく、ヒープを使うこともできます。ヒープを使うこともできるのですが、そうするとプログラムが遅くなるからです。

C++の実装では、後で不正に取りに来れるように、スタックに残したゴミをそのままにしておく必要はありません。コンパイラが、今空けた部屋の中のすべてをゼロに戻すコードを生成することは完全に合法です。しかし、そうしないのは、やはりコストがかかるからです。

C++の実装は、スタックが論理的に縮小したときに、以前有効だったアドレスがまだメモリにマッピングされていることを確認する必要はありません。実装はオペレーティングシステムに、「このページのスタックはもう使いません」と伝えることができます。もし誰かが以前に有効であったスタックページに触れたら、例外を発行してプロセスを破壊してください。 繰り返しになりますが、実装は遅く、不要なため、実際にはこれを行いません。

そのかわり、実装では間違いを犯しても逃げられるようになっています。たいていの場合は。ある日、本当にひどいことが起きて、プロセスが爆発するまでは。

これが問題なのです。ルールがたくさんあるので、うっかり破ってしまいがちなのです。確かに私は何度も破っています。さらに悪いことに、この問題が表面化するのは、メモリが破損してから数十億ナノ秒後に破損が検出され、誰がそれを台無しにしたかを把握するのが非常に困難な場合です。

メモリセーフな言語ほど、パワーを制限することでこの問題を解決しています。通常のC#では、ローカルのアドレスを取得し、それを返したり、後で保存したりすることはできません。ローカルのアドレスを取得することはできますが、ローカルのライフタイムが終了した後にそれを使用することは不可能であるように、この言語は巧妙に設計されています。ローカルのアドレスを取って返すには、コンパイラを特別な "unsafe" モードにする必要があります。 という言葉をプログラムに入れることで、ルールを破るような危険なことをしているのではという注意を喚起することができます。

詳しくはこちら

  • もしC#が参照を返すことができたらどうだろう?偶然にも、これが今日のブログ記事の主題です。

    https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

  • なぜスタックを使ってメモリを管理するのですか?C#の値型は常にスタックに格納されるのか?仮想メモリはどのように機能するのか?などなど、C#のメモリマネージャの仕組みに関するトピックを多数ご紹介しています。これらの記事の多くは、C++プログラマにとっても切実なものです。

    https://ericlippert.com/tag/memory-management/