私はCでいくつかのトレーニング資料を準備していますが、典型的なスタックモデルに適合するように例を求めています。
Cスタックは、Linux、Windows、Mac OSX(PPCおよびx86)、Solaris、および最新のUnixでどの方向に成長しますか?
スタックの成長は通常、オペレーティングシステム自体に依存するのではなく、実行されているプロセッサに依存します。たとえば、Solarisはx86およびSPARCで実行されます。 Mac OSX(あなたが言ったように)はPPCとx86で動作します。Linuxは仕事中の私の大きなhonkin 'System zから 小さな小さな腕時計 まですべてで動作します。
CPUが何らかの選択肢を提供する場合、OSで使用されるABI /呼び出し規約は、コードが他のすべてのコードを呼び出すようにする場合にどの選択を行う必要があるかを指定します。
プロセッサとその方向は次のとおりです。
これらの最後のいくつかの私の年齢を示す1802は、初期のシャトルを制御するために使用されたチップでした(ドアが開いているかどうか、それが持っていた処理能力に基づいて:-)と私の2番目のコンピューター、 COMX-35 (私の ZX8 の後に)。
here から収集したPDP11の詳細、 here から収集した8051の詳細。
SPARCアーキテクチャは、スライディングウィンドウレジスタモデルを使用します。アーキテクチャ的に表示される詳細には、有効で内部的にキャッシュされたレジスタウィンドウの循環バッファも含まれます。 ここ 詳細。 SPARCv8マニュアルで説明 のように、SAVEおよびRESTORE命令はADD命令とレジスタウィンドウの回転に似ています。通常の負の代わりに正の定数を使用すると、上向きになります。成長しているスタック。
前述のSCRTテクニックは別のものです-1802は、SCRTに16ビットの16ビットレジスタの一部または16個を使用しました(標準の呼び出しおよびリターンテクニック)。 1つはプログラムカウンターで、SEP Rn
命令でPCとして任意のレジスタを使用できます。 1つはスタックポインターで、2つは常にSCRTコードアドレスを指すように設定されていました。1つは呼び出し用、もう1つは戻り用です。 Noレジスタは特別な方法で処理されました。これらの詳細はメモリからのものであり、完全に正しいとは限らないことに注意してください。
たとえば、R3がPC、R4がSCRTコールアドレス、R5がSCRTリターンアドレス、R2が「スタック」(ソフトウェアに実装されているとおりの引用)であった場合、SEP R4
はR4をPCに設定します。 SCRT呼び出しコードの実行を開始します。
次に、R3をR2の「スタック」に格納し(R6は一時ストレージに使用されたと思います)、上下に調整し、R3に続く2バイトを取得し、ロードしますintoR3、次にSEP R3
を実行し、新しいアドレスで実行します。
戻るには、SEP R5
が古いアドレスをR2スタックから引き出し、2を追加して(呼び出しのアドレスバイトをスキップする)、R3にロードし、SEP R3
を実行して、前のコード。
最初にすべての6502/6809/z80スタックベースのコードの後に頭を包み込むのは非常に難しいが、壁にぶち当たっているような方法でエレガントなままです。また、チップの大きな販売機能の1つは、16個の16ビットレジスタの完全なスイートでしたが、そのうち7個がすぐに失われました(SCRTでは5個、DMAおよび記憶)。ああ、現実に対するマーケティングの勝利:-)
System zは実際には非常によく似ており、R14およびR15レジスタを呼び出し/戻りに使用します。
C++(Cに適応可能) stack.cc :
static int
find_stack_direction ()
{
static char *addr = 0;
auto char dummy;
if (addr == 0)
{
addr = &dummy;
return find_stack_direction ();
}
else
{
return ((&dummy > addr) ? 1 : -1);
}
}
ダウンするメリットは、古いシステムでは通常スタックがメモリの最上位にあったことです。プログラムは通常、メモリを一番下から埋めていたため、この種のメモリ管理は、スタックの一番下を適切な場所に測定して配置する必要性を最小限に抑えました。
X86でスタックが大きくなります(アーキテクチャによって定義され、スタックポインタはインクリメントされ、プッシュはデクリメントされます)。
MIPSにはPush
/pop
命令はありません。すべてのプッシュ/ポップは、スタックポインターに対して相対的にロード/ストアによって明示的に行われ、その後手動で$sp
ポインター。ただし、すべてのレジスタとして($0
)は、理論上は汎用ですany registerはスタックポインタになることができ、プログラマが望む任意の方向にスタックを拡張できます。 MIPS ABIは通常、下方に成長します。
Intel 8051では、おそらくメモリスペースが非常に小さく(元のバージョンでは128バイト)、ヒープがなく、スタックをヒープから分離するためにスタックを上に置く必要がないため、スタックが大きくなります下から。
ほとんどのシステムでは、スタックが大きくなります。 https://Gist.github.com/cpq/8598782 にある私の記事では、スタックが大きくなる理由について説明しています。その理由は、成長している2つのメモリ領域(ヒープとスタック)の最適なレイアウトだからです。
プログラムに割り当てられたメモリには「永久データ」、つまりプログラム自体のコードが下部にあり、次にヒープが中央にあるため、サイズが小さくなります。スタックを参照するための別の固定ポイントが必要であるため、最上位になります。これは、ヒープが潜在的にヒープ上のオブジェクトに隣接するまで、スタックが成長することを意味します。
私が見る限り、この点に触れていない他の回答へのほんの小さな追加:
スタックを下方に拡張すると、スタック内のすべてのアドレスがスタックポインターに対して正のオフセットを持ちます。負のオフセットは、未使用のスタックスペースのみを指すため、必要ありません。これにより、プロセッサがスタックポインタ相対アドレッシングをサポートしている場合、スタックの場所へのアクセスが簡単になります。
多くのプロセッサには、あるレジスタに対して正のオフセットのみでアクセスできる命令があります。それらには、いくつかの古いアーキテクチャだけでなく、多くの近代的なアーキテクチャが含まれています。たとえば、ARM Thumb ABIは、単一の16ビット命令Word内でエンコードされた正のオフセットを持つスタックポインター相対アクセスを提供します。
スタックが上向きに成長した場合、スタックポインターに関連するすべての有用なオフセットは負になり、直感的ではなく、利便性が低下します。また、構造体のフィールドへのアクセスなど、レジスタ相対アドレス指定の他のアプリケーションとも対立します。
このマクロは、実行時にUBなしで検出する必要があります。
#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);
__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) {
return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}