最近のほとんどのシステムでスタックが増加する方向は何ですか?


102

Cでいくつかのトレーニング資料を準備していますが、私の例を典型的なスタックモデルに適合させたいです。

Linux、Windows、Mac OSX(PPCおよびx86)、Solaris、および最新のUnixでCスタックはどの方向に成長しますか?


回答:


147

スタックの増加は通常、オペレーティングシステム自体に依存するのではなく、実行しているプロセッサに依存します。たとえば、Solarisはx86とSPARCで動作します。Mac OSX(前述のとおり)はPPCおよびx86で動作します。Linuxは、仕事中の大きなhonkin 'System zから、ちっぽけな小さな腕時計まで、あらゆるもので動作します。

CPUが任意の種類の選択を提供する場合、OSで使用されるABI /呼び出し規約により、コードで他のすべてのコードを呼び出す場合にどの選択を行う必要があるかが指定されます。

プロセッサとその方向は次のとおりです。

  • x86:ダウン。
  • SPARC:選択可能。標準のABIはダウンを使用します。
  • PPC:ダウンだと思います。
  • System z:リンクされたリストでは、私はあなたをからかいます(しかし、少なくともzLinuxの場合はまだダウンしています)。
  • ARM:選択可能ですが、Thumb2にはダウンのみのコンパクトエンコーディングがあります(LDMIA =後のインクリメント、STMDB =前のデクリメント)。
  • 6502:ダウン(ただし256バイトのみ)。
  • RCA 1802A:SCRTの実装に応じて、任意の方法で。
  • PDP11:ダウン。
  • 8051:上。

これらの最後の数人に私の年齢を示すと、1802は初期のシャトルを制御するために使用されたチップでした(ドアが開いているかどうかを検知しました、それが持っていた処理能力に基づいて、私は疑います:-)と私の2番目のコンピューター、COMX-35(私のZX80に続く)。

PDP11の詳細はここから収集さ、8051の詳細はここから収集されます

SPARCアーキテクチャは、スライディングウィンドウレジスタモデルを使用します。アーキテクチャ上で表示される詳細には、有効で内部的にキャッシュされるレジスタウィンドウの循環バッファも含まれ、オーバーフローまたはアンダーフローが発生するとトラップが発生します。詳細はこちらをご覧ください。以下のようSPARCv8マニュアルが説明し、SAVEおよびRESTORE命令はADD命令のようなもので、プラス登録ウィンドウの回転を。通常の負の代わりに正の定数を使用すると、上向きに成長するスタックが得られます。

前述のSCRT手法は別の手法です。1802は、SCRT(標準の呼び出しと戻りの手法)に16ビットの16個のレジスタをいくつか使用しました。1つはプログラムカウンターで、SEP Rn命令を備えたPCとして任意のレジスタを使用できます。1つはスタックポインターで、2つは常にSCRTコードアドレスを指すように設定されています。1つは呼び出し用、もう1つは戻り用です。レジスターは特別な方法で扱われませんでした。これらの詳細はメモリからのものであり、完全に正しくない場合があることに注意してください。

たとえば、R3がPC、R4がSCRT呼び出しアドレス、R5がSCRT戻りアドレス、R2が「スタック」(ソフトウェアに実装されている引用符)の場合、SEP R4R4をPCに設定し、SCRTの実行を開始します。呼び出しコード。

その後、R2「スタック」(私はR6は、一時保管のために使用されたと思います)にR3を保存し、それを調整したり、ダウン、R3、次の2つのバイトをつかむ、それらをロードする、次に行う、R3 SEP R3および新しいアドレスで実行されています。

戻るにはSEP R5、R2スタックから古いアドレスをプルし、2を追加して(呼び出しのアドレスバイトをスキップする)、それをR3にロードしSEP R3て、前のコードの実行を開始します。

すべての6502/6809 / z80スタックベースのコードの後、最初に頭を回すのは非常に困難ですが、壁にぴったりの方法でエレガントです。また、チップの大きな売りの特徴の1つは、16個の16ビットレジスタのフルスイートでしたが、そのうち7個(SCRT用に5個、DMA用に2個、メモリからの割り込み用)がすぐに失われました。ああ、現実に対するマーケティングの勝利:-)

System zは実際には非常によく似ており、R14およびR15レジスターを呼び出し/戻りに使用します。


3
リストに追加するために、ARMはどちらの方向にも拡張できますが、特定のシリコン実装によって一方または他方に設定できます(またはソフトウェアで選択可能にすることもできます)。私が扱ったいくつかは、常にグローダウンモードでした。
マイケルバー

1
これまで見てきたARMの世界(ARM7TDMI)では、スタックはすべてソフトウェアで処理されます。戻りアドレスは、必要に応じてソフトウェアによって保存されるレジスターに格納され、事前/事後のインクリメント/デクリメント命令により、それと他のものをスタックにどちらの方向にも置くことができます。
starblue 2009年

1
HPPAの1つで、スタックは大きくなりました。合理的に近代的な建築ではかなり珍しい。
tml

2
好奇心旺盛な方のために、z / OSでのスタックの動作に関する優れたリソースを以下に示します。www
Dillon

1
理解してくれてありがとう@paxdiablo そのようなコメントをするとき、特にそれが古いコメントであるとき、人々はそれを個人的な侮辱とみなします。私は過去に同じ間違いをしたことがあるので、違いがあることだけを知っています。気を付けて。
CasaDeRobison

23

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);
    }
}

14
うわー、「auto」というキーワードを見たのは久しぶりだ。
paxdiablo 2009年

9
(&dummy> addr)は未定義です。2つのポインターを関係演算子にフィードした結果は、2つのポインターが同じ配列または構造内を指している場合にのみ定義されます。
sigjuice 2009年

2
自分のスタックのレイアウト(C / C ++ではまったく指定されていないもの)を調査しようとすることは、そもそも "移植不可能"なので、私はそれについては特に気にしません。ただし、この関数は正しく1回しか機能しないようです。
ephemient 2009年

9
staticこのためにを使用する必要はありません。代わりに、アドレスを引数として再帰呼び出しに渡すことができます。
R .. GitHub STOP HELPING ICE

5
さらに、を使用するとstatic、これを複数回呼び出すと、後続の呼び出しが失敗する可能性があります...
Chris Dodd

7

成長することの利点は、古いシステムではスタックが通常メモリの最上位にあったことです。通常、プログラムはメモリを下から埋めるので、この種のメモリ管理により、スタックの底を測定して適切な場所に配置する必要性が最小限に抑えられました。


3
「利点」ではなく、トートロジーです。
ローンの侯爵

1
トートロジーではありません。重要なのは、@ valenokが指摘したように、2つのメモリ領域が干渉しないことです(とにかくメモリがいっぱいでない限り)。
YvesgereY

6

スタックはx86で大きくなります(アーキテクチャによって定義され、スタックポインターのポップインクリメント、プッシュデクリメント)。


5

MIPSおよび多くの最新のRISCアーキテクチャー(PowerPC、RISC-V、SPARCなど)にはpushpop説明はありません。これらの操作は、スタックポインターを手動で調整してから、調整されたポインターに対して相対的に値をロード/保存することによって明示的に行われます。すべてのレジスター(ゼロ・レジスターを除く)は汎用であるため、理論的にはどのレジスターもスタック・ポインターにすることができ、スタックはプログラマーが望む任意の方向に拡張できます

とは言っても、ほとんどのアーキテクチャでは、スタックとプログラムデータまたはヒープデータが大きくなり、互いに衝突するケースを避けるために、スタックは通常大きくなります。sh-の回答に言及されている大きな対処の理由もあります。いくつかの例:MIPS ABIは下向きに成長し、スタックポインタとして$29(AKA $sp)を使用します。RISC -VABIも下向きに成長し、スタックポインタとしてx2を使用します

Intel 8051ではスタックが大きくなります。これはおそらくメモリ領域が非常に小さい(元のバージョンでは128バイト)ためにヒープがなく、ヒープの増加から分離するためにスタックを上に置く必要がないためです。下から

さまざまなアーキテクチャでのスタックの使用に関する詳細については、https://en.wikipedia.org/wiki/Calling_conventionを参照してください。

こちらもご覧ください


2

他の回答へのほんの少しの追加ですが、私が見る限り、この点には触れていません。

スタックを下向きに成長させると、スタック内のすべてのアドレスがスタックポインタに対して正のオフセットになります。負のオフセットは未使用のスタックスペースのみを指すため、必要ありません。これにより、プロセッサがスタックポインタ相対アドレッシングをサポートしている場合、スタック位置へのアクセスが簡単になります。

多くのプロセッサには、一部のレジスタに対して正のオフセットのみでアクセスを許可する命令があります。それらには、いくつかの古いアーキテクチャだけでなく、多くの近代的なアーキテクチャが含まれます。たとえば、ARM Thumb ABIは、単一の16ビット命令ワード内にエンコードされた正のオフセットを使用したスタックポインタ相対アクセスを提供します。

スタックが上方向に大きくなると、スタックポインタに対するすべての有用なオフセットが負になり、直感的でなく便利ではなくなります。また、たとえば、構造体のフィールドにアクセスするためのレジスタ相対アドレッシングの他のアプリケーションと矛盾しています。


2

ほとんどのシステムでは、スタックが減少します。https: //gist.github.com/cpq/8598782にある私の記事では、スタックが減少する理由について説明しています。それは簡単です。2つのメモリブロック(ヒープとスタック)を固定メモリチャンクにレイアウトする方法は?最善の解決策は、それらを反対側の端に置き、お互いに向かって成長させることです。


その要点は今死んでいるようです:(
Ven

@Ven-私はそれに到達できます
Brett Holman

1

プログラムに割り当てられたメモリには「永続的なデータ」、つまりプログラム自体のコードが一番下にあり、中央にヒープがあるため、サイズが小さくなります。スタックを参照するための別の固定点が必要になるので、トップが残ります。これは、スタックがヒープ上のオブジェクトに隣接する可能性があるまで成長することを意味します。


0

このマクロは、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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.