私は基本的に私の理解は、スタックがローカル変数が含まれていることであるDRAM内のスタックとは何ですか(再帰中に何が起こるか)。
(つまり、あなたは、コード/テキスト、ヒープ、データ、およびスタックを持っている)、スタックはアドレス空間にあるものの理解を取得したいです、しかし、データに含まれる内容とスタックに含まれる内容の違いは何ですか?データ変数でもありませんか?
プログラムに関数a()の再帰呼び出しがある場合、すべてのレベルの再帰で新しいスタックが存在することを意味しますか?
私は基本的に私の理解は、スタックがローカル変数が含まれていることであるDRAM内のスタックとは何ですか(再帰中に何が起こるか)。
(つまり、あなたは、コード/テキスト、ヒープ、データ、およびスタックを持っている)、スタックはアドレス空間にあるものの理解を取得したいです、しかし、データに含まれる内容とスタックに含まれる内容の違いは何ですか?データ変数でもありませんか?
プログラムに関数a()の再帰呼び出しがある場合、すべてのレベルの再帰で新しいスタックが存在することを意味しますか?
スタックは通常、データの使用方法と管理方法だけが異なります。非ローカル変数自体には通常、特定のメモリ位置が知られていますが、スタック上のものはレジスタ(スタックポインタまたはベースポインタなど)に関連して検出されます。
通常、スタックにはローカル変数、渡されたパラメータ、およびスタック自体を管理するための制御情報が含まれています。
再帰呼び出しを行うと、新しいスタックを取得せず、新しいスタックフレームだけを取得します。フレームは、現在のスタックの深さに関連するスタックのチャンクです(再帰関数であれ、通常の関数呼び出しであれ)。これは再帰を可能にするものであり、与えられた深度の変数が他の深度の変数とは独立しているという事実です。
これはもちろん、すべてアーキテクチャに依存していることに注意してください。上記の私の説明は一般的なケースですが、SPARC、System z、RCA1802など、スタックが異なる方法で実行されるアーキテクチャがあります。
次のプログラムは、何が起こっているのか理解するのに役立ちます。テキスト、bss、ヒープ、およびスタックのポインターの例が表示されます。テキストは通常実行可能なコード、bssは静的/大域変数、ヒープは動的に割り当てられたメモリ、スタックはローカル変数を含んでいます。
#include <stdlib.h>
#include <stdio.h>
#define TESTS 10
int numfeed = 0;
int numdead = 0;
recurse(int x)
{
u_int pattern=0xfeedface;
u_int *otherpattern = malloc(4);
*otherpattern = 0xdeadbeef;
printf("Feedface %d is at %p\n",x,&pattern);
printf("deadbeef %d is at %p\n",x,otherpattern);
if (--x == 0)
{
int *off;
for(off = &pattern;numfeed<TESTS;off++)
{
if (*off == 0xfeedface)
printf("Found feedface #%d at %p\n", ++numfeed, off);
if (*off == 0xdeadbeef)
printf("Found deadbeef #%d at %p -- WHAT?!?!!?!?\n", ++numdead, off);
}
}
else
{
recurse(x);
}
// Not freeing otherpattern intentionally.
}
main()
{
u_int *otherpattern = malloc(4);
*otherpattern = 0xdeadbeef;
int *off;
recurse(TESTS);
for(off = otherpattern+1;numdead<TESTS;off++)
{
if (*off == 0xfeedface)
printf("Found feedface #%d at %p -- WHAT?!?!!!?!?\n", ++numfeed, off);
if (*off == 0xdeadbeef)
printf("Found deadbeef #%d at %p\n", 1+TESTS-(++numdead), off);
}
printf("numfeed is at %p\n",&numfeed);
printf("recurse is at %p\n",&recurse);
}
まず、小さな明確化。スタックは必ずしもDRAMにあるわけではありません。それらは、DRAM、キャッシュ、ディスクのいずれのメモリにも形成できる単なる構造体です。
スタックを理解するには、スタックとは何かを理解する必要があります。これは、トレイのスタックのようなものです、それはスタックにする特性は以下のとおりです。
何かをスタックに格納することをPUSHといい、それを削除することをPOPといいます。私は空のスタックに次ないと言う:
PUSH A PUSH B PUSH C
次にスタックが、私は(ここにはオペランドはありません注意してください)POPを実行した場合さて、それはCを返します
C - Top B A
が含まれておりますスタックが含まれます
B -- top of stack A
スタックは、上記のアルゴリズムのハードウェア実装です。
レジスタは、スタック・ポイントと呼ばれるスタックの上部のアドレスを含むISA(命令セットアーキテクチャ)Iは、上記示したように、スタック変数にアクセスするPUSHおよびPOP命令を提供 。
これは非常に便利な構成です。スタックはローカル変数、基本的には関数呼び出しの終わりに削除したい一時的なデータを格納するために使われます。特に、関数呼び出しに役立ちます。関数が呼び出されると、新しく呼び出された関数のローカル変数の変数がスタックにプッシュされます。
foo(){
int a;
int b; // both registers containing a and b are PUSHed here on the stack
c = bar(); // pop the stack to get value of c
print c
}
bar(){
int z; // local variables pushed on top of the stack
z = ...
return z; // pop all local variables of bar(), then push z on the stack
}
私は上記が役立つことを願っています。
私の想いから、この答えはいくつかの具体的な詳細では間違っていますが、本質的にコンセプトは正しいです。特に、スタックを使用して変数を返すのは一般的ではなく、使用されている呼び出し規約に従って、特定のレジスタに戻り値をロードすることによって行われます。 –
興味深い点。戻り値としてスタックを使用しない理由はありません。スタックを使用するかどうかは実際にはコンパイラのプロパティであり、スタックのセマンティクスやプログラムの動作とは何の関係もありません。実際、モトローラのHC12用コンパイラはこのスタックを使用していました。レジスタを使用することは、冗長プッシュとポップを保存する最適化です。十分なレジスタがない場合、スタックを使用することは時々OKです。 GCCはこれを時折(まれに)行います。なぜなら、6812がスタックを使用する理由は、レジスタが非常に少ないためです。好奇心が強い、私の答えが間違っているのは他のどのような方法でしたか? –
'int z'がスタックにzをプッシュし、' return z'もスタックにzをプッシュすると、戻り値を破棄することなくマシンコードが復帰した後に 'SP'をリセットするのに問題があります。 'RET'命令の前のコードは確かにそれを行うことはできませんが、起動したコードは呼び出されたサブルーチンについてあまりにも多くの知識を必要とします(レジスタに' SP'の元の値を格納しない限り)。 –
スタックはDRAMに固有のものではありません。それらはデータ構造です。関数呼び出しと再帰を容易にするために、プロセッサによって実装されています。私は以下にいくつかの詳細を掲載しました。 –