こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

消滅したローカル変数のメモリ領域への上書きについて

Cプログラミング初心者のものです。
課題でわからないところがあるので質問いたします。

以下ソース

typedef struct comp{
int val1;
int val2;
}*comp_t,comp_val;

comp_t first();
void second();

int main(int argc, char **argv){
comp_t ret;
ret=first(val);
second();
printf("Val1=%d,Val2=%d\n",ret->val1,ret->val2);
}

comp_t first(){
comp_val comp;
comp.val1=10;
comp.val2=20;
return ∁
}

void second(){
int a=-100;
int b =-200;
}

実行すると
Val1=-200,Val2=-100
と出ます。ローカル変数が消滅し、second()で新たなintを二つ定義しているので、キレイに上書きされて結果が出たことはわかるのですが、
それなら普通、-100,-200の順に出るのではないのでしょうか。
なぜ裏返ってしまったのでしょう?
また、それはソースを見ただけでわかるものなのでしょうか。
返信お願いします。

投稿日時 - 2008-07-20 11:15:07

QNo.4190402

すぐに回答ほしいです

質問者が選んだベストアンサー

思いっきり処理系依存な話ですが、一般的なC言語処理系というかコンパイラの出力では、そのような答えになる場合が多いと思います。
なぜそうなるのかはC言語のソースではなく、アセンブラ出力を見れば確実にわかりますが、

・C言語のたいていの処理系(コンパイラ)では、自動変数は「スタック」を使って実現しています。
・大抵の実行環境(CPU)では、スタックはアドレスの「後ろ」から順に使っていくようになっています。

以下、int が4バイトとして書きます。
・first関数
スタックの末尾に8バイトを自動変数 comp 用に確保します。
その8バイト内では、構造体のメンバが val1 、val2 の順に並んでますから、
val1 のアドレス: スタック末尾-8バイト(変数の確保位置)+0バイト(構造体内での位置)
val2 のアドレス: スタック末尾-8バイト(変数の確保位置)+4バイト(構造体内での位置)= スタック末尾-4バイト
になります。

・second 関数
変数a、変数bの順にスタック末尾から4バイトずつ確保されます。
a のアドレス: スタック末尾-4バイト
b のアドレス: aのアドレス-4バイト = スタック末尾-8バイト

結果、comp.val1 とb、comp.val2 とa がスタック上の同じアドレスに割り当てられることになります。

main 関数内で、 printf("&argc=%p, &argv=%p, &ret=%p\n", &argc, &argv, &ret);
first 関数内で、printf("&comp=%p, &comp.val1=%p, &comp.val2=%p\n", &comp, &(comp.val1), &(comp.val2));
second 関数内で、 printf("&a=%p, &b=%p\n", &a, &b);

というコードを追加してみれば、どんな感じでスタックが消費されてアドレスが変わっていくのかが少しは見えると思います。

投稿日時 - 2008-07-20 13:53:09

お礼

大変遅くなってしまいましてごめんなさい、解答ありがとうございます。
追加コードありがとうございます。さっそく試してみます。

投稿日時 - 2010-10-19 13:33:04

ANo.4

このQ&Aは役に立ちましたか?

16人が「このQ&Aが役に立った」と投票しています

回答(4)

ANo.3

>ソースを見ただけでわかるものなのでしょうか
わかりません.
ただ,処理系依存なので,処理系を併記すればわかる人はわかります.

関数を呼ぶときは,スタックに制御情報が積まれて,引数が積まれて,ローカル変数が積まれる.
(スタックだと思ってください.
 | firstのローカル変数
 | firstの引数
 | firstの制御情報
 | mainの情報
first関数が終わってmainに戻ったら
 | 
 | 
 | 
 | mainの情報
となるが,firstの情報は削除されることなく残っている.
ローカル変数のポインタを取得しておけば
後に積まれることになるであろうsecondのローカル変数の位置がわかる.
で,secondを呼び出すと
 | secondのローカル変数
 | secondの引数
 | secondの制御情報
 | mainの情報
ここで,secondから戻ってきた直後は,secondのローカル変数の値は保持されている.
firstを読んだ時に,ローカル変数へのポインタわかっているので,プログラムのように出力が可能.
処理系依存なのですべてのコンパイラで可能なわけではありません.
このあたりのことをふまえた上での課題ですかね?
違うなら,前の回答者さんがおっしゃってる通り
ローカル変数へのポインタ参照は危ないのでやめましょう
ってことで終わりです.

上のようなことを考えてなら,次のような理由だと思います.
aとbでは,スタックにa,bの順番で積み
 | b = -200
 | a = -100
 | 制御情報
 | mainの情報
と積まれるが
comp_val は構造体なので
 | comp_val.val1
 | comp_val.val2
 | 制御情報
 | mainの情報
comp_valという1つの塊としてスタックに積まれてしまったため順番が逆になったものと思います.

投稿日時 - 2008-07-20 12:09:17

お礼

大変遅くなってしまいましてごめんなさい、解答ありがとうございます。
構造体と普通の値ではスタックの積み方が異なるんですね...詳しい説明ありがとうございました。

投稿日時 - 2010-10-19 13:32:00

ANo.2

バグはNo.1さんのおっしゃる通りとして、
多分、わざと危険なことをしてローカル変数を潰す
実験だと思います。

それで、No.1さんのおっしゃる通り、
”いつ解放されるかわからないローカル変数のポインタを返してはいけません。”
がプログラマの鉄則で、こういうプログラムは通常存在しません。
あくまで、ローカル変数のメモリ領域への上書きの実験的意味合いが強いのでしょう。

順序が逆になる点は、多分、Cのコンパイルプログラムの作りの問題で、structのときだけ、たまたま、メモリの割りあて順序を逆にしているというだけで、そのような規則はないと思いますよ。

したがって、”それはソースを見ただけでわかるものなのでしょうか。”についてはわからないと思います。

ちなみに言えば、Cのコンパイルプログラムの作りによって、
ローカル変数は、上記プログラムで、必ず潰れるとも限らないと思います。

投稿日時 - 2008-07-20 12:01:31

お礼

大変遅くなってしまいましてごめんなさい、解答ありがとうございます。
有名なプログラミング言語でも、規則通りに作らないとランダム性が出てしまうんですね。

投稿日時 - 2010-10-19 13:26:39

ANo.1

> ret=first(val);

変数valの宣言がどこにもありませんね、というかfirstは引数を取らないんでは。

> comp_t first(){
> comp_val comp;
> comp.val1=10;
> comp.val2=20;
> return ∁
> }

いつ解放されるかわからないローカル変数のポインタを返してはいけません。メモリ領域を破壊しかねない危険行為です。

> void second(){
> int a=-100;
> int b =-200;
> }

これではsecond()も新たにローカル変数を作ってそこに値を入れてるだけなので、ret(first()内のcomp)のメンバーには影響を与えません。
本当にこのソースで完全(first()に絡む間違いはさておき)なのであれば、「たまたまval1,val2(がかつて存在したアドレス)にその値が入っていた」だけにしか見えません。

投稿日時 - 2008-07-20 11:41:25

補足

すみません。first(val)はfirst()の間違いでした。
このソースで、printfがどのように表示されるか、というのが課題です。
ローカル変数のポインタを返しているのはわざとだと思われます。

投稿日時 - 2008-07-20 11:43:20

あなたにオススメの質問