ローカル変数のメモリにスコープ外でアクセスできますか?


1029

次のコードがあります。

#include <iostream>

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

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

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

出力は 58

それはどのようになりますか?ローカル変数のメモリは、その関数の外ではアクセスできませんか?


14
これはそのままではコンパイルできません。非形成ビジネスを修正しても、gccは引き続き警告しaddress of local variable ‘a’ returnedます。valgrindショーInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
11

76
@Serge:私の青年期に、Netwareオペレーティングシステム上で実行される、オペレーティングシステムによって厳密に認可されていない方法でスタックポインタを巧みに移動することを含む、ちょっとトリッキーなゼロリングコードに取り組んだことがありました。多くの場合、スタックが画面のメモリと重なってしまい、バイトがディスプレイに正しく書き込まれるのを見ることができるため、いつミスを犯したかわかります。最近はそんなことは避けられません。
Eric Lippert、2011年

23
笑。問題がどこにあるのかを理解する前に、質問といくつかの回答を読む必要がありました。それは実際に変数のアクセススコープに関する質問ですか?関数の外で 'a'を使用することすらありません。これですべてです。一部のメモリ参照を回避することは、変数のスコープとはまったく異なるトピックです。
erikbwork

10
重複回答は重複質問を意味するものではありません。ここで人々が提案した多くの重複した質問は、たまたま同じ根本的な症状に言及している完全に異なる質問です...しかし、質問者はそれを知る方法を知っているので、彼らは開いたままにすべきです。古いデュープを閉じて、この質問にマージしました。非常に良い答えがあるため、開いたままにする必要があります。
ジョエルスポルスキー

16
@ジョエル:ここでの答えが良い場合は、古い質問マージする必要があります。これはまぎれものであり、その逆ではありません。そして、この質問は確かに、ここで提案された他の質問といくつかの質問の重複です(提案されたいくつかは他のものよりも適切ですが)。エリックの答えは良いと思います。(実際、私は古い質問を救済するために古い質問の1つに回答をマージするためにこの質問にフラグを付けました。)
sbi

回答:


4801

それはどのようになりますか?ローカル変数のメモリは、その関数の外ではアクセスできませんか?

あなたはホテルの部屋を借ります。ベッドサイドテーブルの上の引き出しに本を入れて、寝ます。あなたは翌朝チェックアウトしますが、鍵を返却することを「忘れて」ください。あなたは鍵を盗みます!

1週間後、ホテルに戻り、チェックインせずに、盗まれた鍵を使って古い部屋に忍び込み、引き出しを調べます。あなたの本はまだそこにあります。びっくり!

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

まあ、明らかにそのシナリオは現実の世界で問題なく起こります。部屋にいる権限がなくなったときに、本が消えるような不可解な力はありません。盗まれた鍵で部屋に入るのを妨げる不思議な力もありません。

ホテルの管理者があなたの本を削除する必要はありません。あなたは彼らと契約を結ばなかった、あなたが物を残しておくと彼らはあなたのためにそれを細断するだろうと言った。盗まれたキーを使って違法に部屋に戻ってそれを取り戻す場合、ホテルのセキュリティスタッフはあなたがこっそり侵入するのを捕まえる必要はありません。あなたが彼らと契約していませんでした。部屋は後で、あなたは私を止める必要があります。」むしろ、あなたは彼らと契約し、あなたが破った契約「私は後で私の部屋にこっそり戻ってこないことを約束する」と言った。

この状況では、何かが起こる可能性があります。本はそこにあることができます-あなたは幸運になりました。他の誰かの本がそこにあり、あなたの本がホテルのかまどにあるかもしれません。あなたが本をばらばらに引き裂いて入ってくると、誰かがそこにいる可能性があります。ホテルはテーブルを削除して完全に予約し、ワードローブに置き換えることができました。ホテル全体が取り壊されてフットボールスタジアムに置き換えられようとしている可能性があり、こっそりと潜っている間に爆発で死ぬことになります。

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

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

更新

聖なる良さ、この答えは多くの注目を集めています。(理由はわかりません-私はそれを単なる「楽しい」小さなアナロジーと考えましたが、何でも。)

もう少し技術的な考えでこれを少し更新することは密接な関係があるのではないかと思いました。

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

1つ目は、ストレージ内の各バイトの「存続時間」、つまり、あるプログラム変数と有効に関連付けられている期間を予測できない、ある種の「長命」のストレージ領域を用意することです。時間の。コンパイラーは、「ヒープ・マネージャー」への呼び出しを生成します。このマネージャーは、ストレージが必要なときに動的に割り振り、不要になったときにそれを再利用する方法を知っています。

2番目の方法は、各バイトの存続期間がよくわかっている「短命」のストレージ領域を用意することです。ここでは、寿命は「ネスト」パターンに従います。これらの短命変数の中で最も長命の変数は、他の短命変数の前に割り当てられ、最後に解放されます。存続期間の短い変数は、存続期間の最も長い変数の後に割り当てられ、それらの前に解放されます。これらの寿命の短い変数の寿命は、寿命の長い変数の寿命内に「ネスト」されます。

ローカル変数は後者のパターンに従います。メソッドに入ると、そのローカル変数が有効になります。そのメソッドが別のメソッドを呼び出すと、新しいメソッドのローカル変数が有効になります。最初のメソッドのローカル変数が死ぬ前に死んでしまいます。ローカル変数に関連付けられたストレージの存続期間の開始と終了の相対的な順序は、事前に計算できます。

このため、スタックには最初にプッシュされたものが最後に取り出されるというプロパティがあるため、ローカル変数は通常「スタック」データ構造のストレージとして生成されます。

それは、ホテルが順番に部屋を貸し出すことだけを決定するようなもので、あなたがチェックアウトした部屋番号よりも大きい部屋番号を持つ全員がチェックアウトするまではチェックアウトできません。

スタックについて考えてみましょう。多くのオペレーティングシステムでは、スレッドごとに1つのスタックが取得され、スタックは特定の固定サイズになるように割り当てられます。メソッドを呼び出すと、スタックにデータがプッシュされます。次に、元の投稿者がここで行うように、スタックへのポインターをメソッドから戻す場合、それは完全に有効な100万バイトのメモリブロックの中央へのポインターにすぎません。私たちの類推では、ホテルをチェックアウトします。あなたがそうするとき、あなたはちょうど最も高い番号の占有された部屋からチェックアウトしました。他の誰もあなたの後にチェックインせず、あなたが違法にあなたの部屋に戻った場合、あなたのすべてのものはこの特定のホテルにまだそこにあることが保証されています

スタックは非常に安くて簡単なので、一時ストアにスタックを使用します。C ++の実装は、ローカルの格納にスタックを使用する必要はありません。ヒープを使用できます。そうしないと、プログラムが遅くなります。

C ++の実装では、スタックに残されたガベージをそのまま残して、後で不正に戻ってくることがないようにする必要はありません。コンパイラーが、空いたばかりの「部屋」のすべてをゼロに戻すコードを生成することは完全に合法です。繰り返しますが、それは高価になるからです。

C ++の実装は、スタックが論理的に縮小したときに、以前は有効であったアドレスが引き続きメモリにマッピングされることを保証するために必要ではありません。実装は、オペレーティングシステムに「このスタックのページを使用して完了しました。別の言い方をするまで、以前に有効なスタックページに誰かが触れた場合にプロセスを破棄する例外を発行する」ことを通知できます。繰り返しになりますが、実装は遅くて不必要なので、実際にはそうしません。

代わりに、実装ではミスを犯してそれを回避できます。ほとんどの時間。ある日まで、本当にひどいことが起こり、プロセスが爆発します。

これは問題があります。多くのルールがあり、それらを誤って破るのは非常に簡単です。確かに何度も持っています。さらに悪いことに、破損が発生してから何十億ナノ秒もメモリが破損していることが検出された場合にのみ問題が表面化します。

より多くのメモリセーフ言語はあなたの力を制限することによってこの問題を解決します。「通常の」C#では、ローカルのアドレスを取得して返すか、後で使用するために保存する方法はありません。ローカルのアドレスを取得できますが、言語は巧妙に設計されているため、ローカルの存続期間が終了すると使用できなくなります。ローカルのアドレスを取得してそれを返すには、コンパイラを特別な「安全でない」モードにし、プログラムに「安全でない」という単語入れて、おそらく実行しているという事実に注意を向ける必要があります。ルールを破る危険な何か。

さらに読むために:

  • C#が参照を返すことを許可した場合はどうなりますか?偶然にも、それが今日のブログ投稿の主題です。

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

  • スタックを使用してメモリを管理する理由 C#の値の型は常にスタックに格納されますか?仮想メモリはどのように機能しますか?また、C#メモリマネージャーの動作に関する多くのトピック。これらの記事の多くは、C ++プログラマーにも密接に関連しています。

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


56
@muntoo:残念ながら、仮想メモリのページを解放または割り当て解除する前に、オペレーティングシステムが警告サイレンを鳴らすようなものではありません。そのメモリをもう所有していないときにそのメモリをいじくりまわしている場合、オペレーティングシステムは、割り当て解除されたページに触れたときにプロセス全体を停止する権利の完全にあります。ブーム!
Eric Lippert、

83
@カイル:安全なホテルだけがそれを行います。安全でないホテルでは、キーのプログラミングに時間を費やす必要がないため、測定可能な利益が得られます。
Alexander Torstling 2011年

498
@cyberguijarro:C ++はメモリセーフではないというのは単なる事実です。何かを「たたく」のではありません。たとえば、「C ++は、脆弱で危険なメモリモデルの上に積み上げられた、仕様が不十分で複雑すぎる機能の恐ろしいミッシュマッシュであり、自分自身の正気のために作業しなくなったことに感謝しています」それはC ++をたたくでしょう。メモリセーフではないことを指摘することは、元の投稿者がこの問題を見ている理由を説明しています。編集ではなく、質問への回答です。
Eric Lippert、2011年

50
厳密に言えば、たとえを言えば、ホテルの受付係があなたが鍵を手に取ることができてとても嬉しかったことを述べるべきです。「ああ、私がこの鍵を持っていってもいいですか?」「どうぞ。なぜ私は気にかけますか?私はここでしか働きません」。使用するまで違法にはなりません。
2011年

140
少なくとも、ある日は本を書くことを検討してください。ブログ記事の改訂版や拡大版だけでも購入するので、きっとたくさんの人に買ってもらえると思います。しかし、プログラミングに関連するさまざまな問題についてのあなたの元の考えが書かれた本は、素晴らしい本になるでしょう。その時間を見つけるのは信じられないほど難しいことは知っていますが、それを書くことを検討してください。
Dyppl

276

ここでは、以前はのアドレスであったメモリの読み取りと書き込みを行っていますa。の外にいるfooので、これはランダムなメモリ領域へのポインタにすぎません。たまたまあなたの例では、そのメモリ領域が存在し、現時点では他に何も使用していません。あなたはそれを使い続けることで何かを壊すことはありません、そして他にまだそれを上書きしていません。したがって、5まだあります。実際のプログラムでは、そのメモリはほとんどすぐに再利用され、これを実行すると何かが壊れます(ただし、症状はかなり後になるまで現れないかもしれません!)

から戻るとfoo、そのメモリをもう使用しておらず、別のメモリに再割り当てできることをOSに伝えます。運が良ければ、再割り当てされることはなく、OSがそれを再び使用することに気付かない場合は、うそをつくことはありません。しかし、そのアドレスで終わる他のものは何でも上書きする可能性があります。

コンパイラが文句を言わないのはなぜでしょうか?それはおそらくfoo、最適化によって排除されたためです。通常、このようなことについて警告します。Cは、あなたが何をしているのかを知っていて、技術的にはここのスコープに違反していない(のa外にそれ自体への参照はない)ことを前提としています。fooメモリアクセスルールのみで、エラーではなく警告のみをトリガーします。

つまり、これは通常は機能しませんが、偶然に機能する場合があります。


152

収納スペースがまだ踏まれていなかったからです。その動作を当てにしないでください。


1
「真実とは何ですか?ピラトを冗談めかして言った」以来、それはコメントを待つ最も長い間でした。多分それはそのホテルの引き出しのギデオンの聖書だった。とにかく彼らに何が起こりましたか?少なくともロンドンでは、もう存在しないことに注意してください。平等法の下では、宗教的管轄の図書館が必要になると思います。
ロブ・ケント

私はそれをずっと前に書いたことを誓ったかもしれませんが、それは最近現れて、私の返事がそこになかったことがわかりました。>
msw

1
はは。イギリスで最も偉大なエッセイストの1人であるフランシスベーコンは、グラバーの息子である国の文法学校の子供が天才である可能性があることを受け入れられないため、シェイクスピアの戯曲を書いたと疑う人もいます。これが英語の授業体系です。イエスは言われた、「私は真実です」。oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
ロブ・ケント

84

すべての答えに少し追加:

あなたがそのようなことをするなら:

#include<stdio.h>
#include <stdlib.h>
int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n",*p);
}

出力はおそらく次のようになります:7

これは、foo()から戻った後、スタックが解放され、boo()によって再利用されるためです。実行可能ファイルを逆アセンブルすると、明確に表示されます。


2
基礎となるスタック理論を理解するための単純ですが、優れた例です。「int a = 5;」と宣言するだけで、1つのテストが追加されます。foo()で「static int a = 5;」として 静的変数のスコープと寿命を理解するために使用できます。
制御

15
-1 " たぶん7になります。" コンパイラーはbooを登録する可能性があります。不要なので削除するかもしれません。* pが5ならない可能性は十分にありますが、7になる可能性があるという特に理由があるわけではありません
Matt

2
これは未定義の動作と呼ばれます!
Francis Cugler、2015年

なぜそしてどのようboofooスタックを再利用しますか?関数スタックは互いに分離されていません。また、Visual Studio 2015でこのコードを実行するとガベージが発生します
ampawd

1
@ampawdほぼ1年前ですが、「関数スタック」は互いに分離されていません。CONTEXTにはスタックがあります。そのコンテキストは、スタックを使用してメインに入り、次に降りてfoo()、存在し、次に降りboo()ます。 Foo()そして、Boo()の両方が、同じ場所で、スタックポインタと入ります。ただし、これは信頼すべき動作ではありません。他の「もの」(割り込みと同様、またはOSは)の呼び出しの間でスタックを使用することができますboo()し、foo()それの内容を変更し、...
ラスシュルツ

72

C ++では、あなたができる任意のアドレスにアクセスし、それはあなたが意味するものではありませんはず。アクセスしているアドレスは無効になりました。それは作品 fooが返された後、他に何もメモリをスクランブルしないが、それは多くの状況下でクラッシュする可能性があるため。Valgrindでプログラムを分析するか、最適化してコンパイルしてみてください...


5
おそらく、任意のアドレスにアクセスを試みることができることを意味します。今日のほとんどのオペレーティングシステムでは、どのプログラムもどのアドレスにもアクセスできないためです。アドレス空間を保護するための保護手段はたくさんあります。これが、別のLOADLIN.EXEが存在しない理由です。
v010dya

67

無効なメモリにアクセスしてC ++例外をスローすることはありません。任意のメモリ位置を参照するという一般的な考えの例を示しているだけです。私はこのように同じことができます:

unsigned int q = 123456;

*(double*)(q) = 1.2;

ここでは、123456をdoubleのアドレスとして扱い、それに書き込みます。さまざまなことが起こり得ます。

  1. q実際には、実際にはdoubleの有効なアドレスである可能性がありますdouble p; q = &p;
  2. q 割り当てられたメモリ内のどこかを指す可能性があり、そこにある8バイトを上書きするだけです。
  3. q 割り当てられたメモリの外を指し、オペレーティングシステムのメモリマネージャーがセグメンテーションフォールト信号をプログラムに送信し、ランタイムがプログラムを終了させます。
  4. 宝くじに当選します。

それを設定する方法は、返されるアドレスがメモリの有効な領域を指していることをもう少し合理的です。それはおそらくスタックの少し下にあるためですが、それでもアクセスできない無効な場所です決定論的なファッション。

通常のプログラムの実行中に、そのようなメモリアドレスのセマンティックな妥当性を自動的にチェックする人は誰もいません。ただし、などのメモリデバッガvalgrindはこれを喜んで行うので、プログラムを実行してエラーを確認する必要があります。


9
私は今このプログラムを実行し続けるプログラムを書くつもりです4) I win the lottery
Aidiakapi

29

オプティマイザを有効にしてプログラムをコンパイルしましたか?このfoo()関数は非常に単純で、結果のコードでインライン化または置き換えられている可能性があります。

しかし、私はマークBに同意します。結果の動作は未定義です。


それは私の賭けです。オプティマイザーは関数呼び出しをダンプしました。
Erik Aronesty

9
それは必要ありません。foo()の後に新しい関数が呼び出されないため、関数のローカルスタックフレームはまだ上書きされていません。foo()の後に別の関数呼び出しを追加5すると、変更されます...
Tomas

プログラムをGCC 4.8で実行し、coutをprintf(およびstdioを含む)に置き換えました。「警告:ローカル変数 'a'のアドレスが返されました[-Wreturn-local-addr]」と正しく警告します。最適化なしで58、-O3で08を出力します。奇妙なことに、その値が0であっても、Pにはアドレスがあります。アドレスとしてNULL(0)が必要でした。
kevinf

23

あなたの問題はスコープとは何の関係もありません。表示するコードでは、関数mainは関数内の名前を認識しfooないためa、foo内でこの名前を使用して直接アクセスすることはできませんfoo

あなたが抱えている問題は、不正なメモリを参照するときにプログラムがエラーを通知しない理由です。これは、C ++標準では、不正なメモリと正当なメモリの明確な境界が指定されていないためです。ポップアウトされたスタックで何かを参照すると、エラーが発生する場合とそうでない場合があります。場合によります。この動作を当てにしないでください。プログラミング時には常にエラーが発生すると想定しますが、デバッグ時にはエラーを通知しないと想定します。


グラフィックメモリの直接操作とIBMのテキストモードビデオメモリのレイアウトが非常に詳細に説明されていたときに、私が以前何らかの方法で遊んだIBMのTurbo Cプログラミングの古いコピーを思い出します。もちろん、コードが実行されたシステムは、それらのアドレスへの書き込みが何を意味するかを明確に定義しました。他のシステムへの移植性について心配しない限り、すべてが問題ありませんでした。IIRC、無効へのポインタはその本の共通のテーマでした。
CVn '23年

@MichaelKjörling:もちろん!人々は時々汚い仕事をしたいです;)
Chang Peng

18

あなたはメモリアドレスを返すだけです、それは許可されていますがおそらくエラーです。

はい、そのメモリアドレスを逆参照しようとすると、未定義の動作が発生します。

int * ref () {

 int tmp = 100;
 return &tmp;
}

int main () {

 int * a = ref();
 //Up until this point there is defined results
 //You can even print the address returned
 // but yes probably a bug

 cout << *a << endl;//Undefined results
}

同意しない:の前に問題がありcoutます。*a割り当てられていない(解放された)メモリを指します。あなたがそれを無視しなくても、それはまだ危険です(そしておそらく偽物です)。
ereOn

@ereOn:問題の意味をより明確にしましたが、有効なc ++コードの点では危険ではありません。しかし、ユーザーがミスを犯して何か悪いことをする可能性が高いという点で危険です。たぶん、たとえば、スタックがどのように成長するかを確認しようとしている場合、アドレス値のみに関心があり、それを逆参照することはありません。
ブライアンR.ボンディ

18

これは、2日前ではなく、ここで説明されている古典的な未定義の動作です。サイトを少し検索してみてください。簡単に言えば、あなたは幸運でしたが、何かが起こった可能性があり、コードがメモリへの無効なアクセスを行っています。


18

Alexが指摘したように、この動作は定義されていません。実際、ほとんどのコンパイラーはこれを実行しないよう警告します。これは、クラッシュを発生させる簡単な方法だからです。

発生する可能性のある不気味な動作の例については、次のサンプルを試してください。

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

void b( int *c )
{
   int y = 29;
   *c = 123;
   cout << "y=" << y << endl;
}

int main()
{
   b( a() );
   return 0;
}

これは「y = 123」を出力しますが、結果は異なる場合があります(本当に!)。ポインタが他の無関係なローカル変数を壊しています。


18

すべての警告に注意してください。エラーを解決するだけではありません。
GCCはこの警告を表示します

警告:ローカル変数 'a'のアドレスが返されました

これがC ++の力です​​。あなたはメモリを気にする必要があります。では-Werrorフラグ、この警告はエラーをbecames、今、あなたはそれをデバッグする必要があります。


17

スタックがそこに配置されてからスタックが(まだ)変更されていないため、機能します。a再度アクセスする前に、他のいくつかの関数(他の関数も呼び出す)を呼び出してください。そうすれば、もうそれほど幸運ではありません... ;-)


16

未定義の動作を実際に呼び出しました。

テンポラリのアドレスを返すことは機能しますが、テンポラリは関数の最後で破棄されるため、それらにアクセスした結果は未定義になります。

そのためa、変更はしませんでしたが、以前とa同じメモリ位置を変更しました。この違いは、クラッシュする場合としない場合の違いによく似ています。


14

典型的なコンパイラの実装では、コードは「以前は a 占められていたアドレスでメモリブロックの値を出力する」と考えることができます。また、ローカルを保持する関数に新しい関数呼び出しを追加する場合int、その値a(またはメモリアドレスa以前にポイントし)が変更されるます。これは、スタックが異なるデータを含む新しいフレームで上書きされるために発生します。

ただし、これは未定義の動作であり、動作に依存するべきではありません。


3
「以前はa 占められていたアドレスを含むメモリブロックの値を出力する」というのは、正しくありません。これにより、彼のコードには明確に定義された意味があるように思えますが、そうではありません。ただし、これはおそらくほとんどのコンパイラーが実装する方法です。
ブレナンヴィンセント

@BrennanVincent:ストレージがによって占有されているa間、ポインタはのアドレスを保持していましたa。標準では、ターゲットの存続期間が終了した後の実装でアドレスの動作を定義する必要はありませんが、一部のプラットフォームでは、環境に特徴的な文書化された方法でUBが処理されることも認識しています。ローカル変数のアドレスは、スコープから外れた後は一般的にあまり役に立ちませんが、他の種類のアドレスは、それぞれのターゲットの存続期間後も意味がある場合があります。
スーパーキャット2018年

@BrennanVincent:たとえば、標準では、渡されたポインタをrealloc戻り値と比較することを実装に要求したり、古いブロック内のアドレスへのポインタを新しいブロックを指すように調整したりすることを要求しない場合がありますが、一部の実装ではそうしています、そしてそのような機能を利用するコードは、たとえ比較さえも、に与えられた割り当てへのポインタを伴うアクションを回避しなければならないコードよりも効率的かもしれませんrealloc
スーパーキャット2018年

14

aスコープ(foo関数)の存続期間中に一時的に割り当てられる変数であるためです。から戻った後fooがメモリは自由であり、上書きすることができます。

あなたがやっていることは、未定義の動作として説明されています。結果は予測できません。


12

:: printfを使用してもcoutを使用しない場合、コンソール出力が正しい(?)ものは劇的に変わる可能性があります。以下のコード内でデバッガーをいじることができます(x86、32ビット、MSVisual Studioでテスト済み):

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}

5

関数から戻った後、メモリの場所に保持された値の代わりにすべての識別子が破棄され、識別子がないと値を見つけることができませんが、その場所には以前の関数によって保存された値がまだ含まれています。

だから、ここの機能は、foo()のアドレスを返すaa、そのアドレスを返した後に破壊されます。そして、その返されたアドレスを通じて変更された値にアクセスできます。

実際の例を見てみましょう。

ある場所にお金を隠し、その場所をあなたに告げたとしましょう。しばらくして、お金の場所を教えてくれた男が亡くなりました。しかし、それでもあなたはその隠されたお金にアクセスできます。


4

これは、メモリアドレスを使用する「ダーティ」な方法です。アドレス(ポインタ)を返すとき、それが関数のローカルスコープに属しているかどうかはわかりません。それは単なるアドレスです。「foo」関数を呼び出したので、「a」のアドレス(メモリロケーション)は、アプリケーション(プロセス)の(現時点では安全に)アドレス指定可能なメモリにすでに割り当てられています。「foo」関数が返された後、「a」のアドレスは「ダーティ」と見なすことができますが、クリーンアップされず、プログラムの他の部分の式によって(少なくともこの特定のケースでは)妨害されたり変更されたりしません。C / C ++コンパイラーは、そのような「ダーティー」アクセスからユーザーを阻止しません(ただし、必要に応じて警告する場合があります)。


1

あなたのコードは非常に危険です。ローカル変数を作成し(関数の終了後に破棄されたと見なされます)、破棄された後、その変数のメモリのアドレスを返します。

つまり、メモリアドレスが有効かどうかにかかわらず、コードはメモリアドレスの問題(セグメンテーション違反など)に対して脆弱になります。

これは、メモリアドレスをポインタに渡すことがまったく信頼できないため、非常に悪いことをしていることを意味します。

代わりにこの例を考えて、それをテストしてください:

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

あなたの例とは異なり、この例ではあなたは:

  • intのメモリをローカル関数に割り当てる
  • そのメモリアドレスは、機能が期限切れになった場合でも有効です(誰も削除しません)
  • メモリアドレスは信頼できる(そのメモリブロックは空きとは見なされないため、削除されるまでオーバーライドされません)
  • メモリアドレスは、使用しない場合は削除する必要があります。(プログラムの最後にある削除を参照してください)

既存の回答でカバーされていないものを追加しましたか?また、生のポインタ/は使用しないでくださいnew
オービットのライトネスレース

1
質問者は生のポインタを使用しました。私は、信頼できないポインタと信頼できるポインタの違いを確認できるようにするために、彼が実行した例を正確に反映した例を実行しました。実際、私のものに似た別の答えがありますが、それはstrcpy wichを使用しています。
Nobun

彼らは使用しませんでしたnew。あなたは彼らにを使うように教えていますnew。ただし、は使用しないでくださいnew
、オービットのライトネスレース

だからあなたの意見では、実際にメモリを割り当てるよりも、関数で破棄されるローカル変数にアドレスを渡す方が良いですか?これは意味がありません。メモリの割り当てと割り当て解除の概念を理解することは重要です。主にポインタについて尋ねる場合は重要です(質問者は新しいポインタを使用せず、使用済みのポインタを使用していました)。
Nobun

いつ言ったの?いいえ、参照されるリソースの所有権を適切に示すには、スマートポインターを使用することをお勧めします。new2019年には使用しないでください(ライブラリコードを作成している場合を除く)。また、初心者にも使用しないでください。乾杯。
、オービットのライトネスレース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.