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の順に出るのではないのでしょうか。
なぜ裏返ってしまったのでしょう?
また、それはソースを見ただけでわかるものなのでしょうか。
返信お願いします。
No.4ベストアンサー
- 回答日時:
思いっきり処理系依存な話ですが、一般的な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);
というコードを追加してみれば、どんな感じでスタックが消費されてアドレスが変わっていくのかが少しは見えると思います。
No.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つの塊としてスタックに積まれてしまったため順番が逆になったものと思います.
大変遅くなってしまいましてごめんなさい、解答ありがとうございます。
構造体と普通の値ではスタックの積み方が異なるんですね...詳しい説明ありがとうございました。
No.2
- 回答日時:
バグはNo.1さんのおっしゃる通りとして、
多分、わざと危険なことをしてローカル変数を潰す
実験だと思います。
それで、No.1さんのおっしゃる通り、
”いつ解放されるかわからないローカル変数のポインタを返してはいけません。”
がプログラマの鉄則で、こういうプログラムは通常存在しません。
あくまで、ローカル変数のメモリ領域への上書きの実験的意味合いが強いのでしょう。
順序が逆になる点は、多分、Cのコンパイルプログラムの作りの問題で、structのときだけ、たまたま、メモリの割りあて順序を逆にしているというだけで、そのような規則はないと思いますよ。
したがって、”それはソースを見ただけでわかるものなのでしょうか。”についてはわからないと思います。
ちなみに言えば、Cのコンパイルプログラムの作りによって、
ローカル変数は、上記プログラムで、必ず潰れるとも限らないと思います。
大変遅くなってしまいましてごめんなさい、解答ありがとうございます。
有名なプログラミング言語でも、規則通りに作らないとランダム性が出てしまうんですね。
No.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(がかつて存在したアドレス)にその値が入っていた」だけにしか見えません。
この回答への補足
すみません。first(val)はfirst()の間違いでした。
このソースで、printfがどのように表示されるか、というのが課題です。
ローカル変数のポインタを返しているのはわざとだと思われます。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# leetcode 155 minstack 1 2022/05/07 16:43
- C言語・C++・C# 宣言する関数の形が決まっている状態で、 str1とstr2の文字列をこの順に引っ付けてstrに保存し 2 2022/05/30 18:21
- C言語・C++・C# C++のcinの動作 5 2023/02/26 00:13
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# スタックフレームの消滅 6 2023/05/20 12:33
- Ruby vscode 文字化け 1 2022/05/21 19:17
- C言語・C++・C# C言語の課題が出たのですが自力でやっても分かりませんでした。 要素数がnであるint型の配列v2の並 3 2022/11/19 17:41
- C言語・C++・C# プログラミングの授業のペーパーテスト 実行結果を答えろ #include int x[ ] = {1 3 2022/06/16 20:08
- C言語・C++・C# 至急教えてください! プログラミングの問題です! お願いします! 出力2と全く同じ出力をするように、 2 2022/06/22 23:10
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
VB.netでDLLを読み込んで実行す...
-
printf / sprintf のスタック消...
-
マス目上の移動のアルゴリズム
-
Cプログラミングの関数電卓のア...
-
C言語・スタックを使用した逆...
-
逆ポーランド記法
-
スタックの仕組み
-
アセンブラでmain関数から作成...
-
CASLとCASL2の違いについて
-
C言語のリスト、スタック、キュ...
-
エラー?メッセージ
-
パソコンでインターネット接続...
-
プログラムの規模を表す単位「k...
-
ライン数とステップ数の違いに...
-
[ASP]If~Else If~End If 対 Case
-
Excelでの統計処理(合計点、平...
-
ubuntuで デイスク/deb/loopと...
-
ステップ数??
-
第一級陸上特殊無線技士
-
乱数をC言語で
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
VB.netでDLLを読み込んで実行す...
-
最大スタックサイズを大きくす...
-
エラー?メッセージ
-
printf / sprintf のスタック消...
-
_CRTIMPの意味は?
-
スタックを用いて整数配列を入...
-
スタックフレームの消滅
-
関数呼び出しでのスタック消費量
-
スタックの伸張方向
-
スタック領域変更
-
逆ポーランド記法
-
関数のプロローグとエピローグ...
-
Ethernetヘッダの取得 NDIS
-
スタックとキューの使い所
-
再帰処理を非再帰処理に書き換...
-
CASLとCASL2の違いについて
-
マス目上の移動のアルゴリズム
-
コンパイラオプション
-
VC++6.0 Stack Overflow !!
-
VCでのスタックサイズ
おすすめ情報