スタック変数へのポインターを返すためにコードが未定義の動作を引き起こしているという事実は別として、f() の署名を変更すると動作が変わる理由を尋ねていました。
その理由
その理由は、コンパイラが関数のスタックフレームを構築する方法にあります。コンパイラが foo() に対して次のようにスタック フレームを構築しているとします。
Address Contents
0x199 local variable p
0x200 Saved register A that gets overwritten in this function
0x201 parameter n
0x202 return value
0x203 return address
f(int m) の場合、スタックは同様に静かに見えます。
Address Contents
0x199 local variable n
0x200 Saved register A that gets overwritten in this function
0x201 parameter m
0x202 return value
0x203 return address
では、foo の 'n' へのポインターを返すとどうなるでしょうか。結果のポインタは 0x201 になります。foo を返すと、スタックのトップは 0x204 になります。メモリは変更されず、値「1」を読み取ることができます。これは、別の関数を呼び出すまで機能します(あなたの場合は「f」)。f を呼び出した後、位置 0x201 はパラメーター m の値で上書きされます。
この場所にアクセスすると (そして printf ステートメントでアクセスすると)、「999」と表示されます。f() を呼び出す前にこの場所の値をコピーしていた場合、値 '1' が見つかります。
この例に固執すると、パラメータが指定されていないため、f() のスタックフレームは次のようになります。
Address Contents
0x200 local variable n
0x201 Saved register A that gets overwritten in this function
0x202 return value
0x203 return address
'1' でローカル変数を初期化しているので、f() を呼び出した後、位置 0x200 で '1' を読み取ることができます。ここで位置 0x201 から値を読み取ると、保存されたレジスタの内容が取得されます。
いくつかの追加ステートメント
- 上記の説明は、観察するものを観察する理由を示す方法論を示すことであることを理解することが重要です。
- 実際の動作は、使用しているツールチェーンと、いわゆる呼び出し規約によって異なります。
- 何が起こるかを予測するのが難しい場合があることは容易に想像できます。これは、メモリを解放した後にメモリにアクセスするのと同様の静かな状況です。そのため、一般的に何が起こるかは予測できません。
- この動作は、最適化レベルを変更することで変更することもできます。たとえば、-O3 をオンにすると、使用されていない変数 n がバイナリに表示されなくなるため、観察結果が異なると想像できます。
- 背後にあるメカニズムを理解すれば、foo から取得したアドレスへの書き込みアクセスが深刻な問題につながる理由が理解できるはずです。
この説明を実験で証明しようとする勇者のために
まず、上記の説明が実際のスタック フレーム レイアウトに依存していないことを確認することが重要です。わかりやすいイラストにするために、レイアウトを紹介しました。
自分のマシンで動作をテストする場合は、お気に入りのデバッガーを使用して、ローカル変数とパラメーターが配置されているアドレスを調べて、実際に何が起こるかを確認することをお勧めします。注意: f の署名を変更すると、スタックに配置される情報が変更されます。したがって、唯一の実際の「移植可能な」テストは、f() のパラメーターを変更し、p が指す値の出力を観察することです。
f(void) を呼び出す場合、スタックに置かれる情報は大幅に異なり、p が指している位置に書き込まれる値は、パラメーターやローカル変数に必ずしも依存しなくなります。また、メイン関数のスタック変数に依存することもあります。
たとえば、私のマシンでは、2 番目のバリアントで読み取った '1' は、n のロードに使用されているように見えるため、'1' を格納するために使用されたレジスタを "num" に保存した結果であることが再現によって明らかになりました。
これがあなたにいくつかの洞察を与えることを願っています。さらに質問がある場合は、コメントを残してください。(これは理解するのが少し奇妙であることを知っています)