109

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

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

4

9 に答える 9

153

スタックの増加は、通常、オペレーティング システム自体には依存しませんが、それが実行されているプロセッサに依存します。たとえば、Solaris は x86 と SPARC で動作します。Mac OSX (おっしゃる通り) は PPC と x86 で動作します。Linux は、仕事中の私の大きな 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 の詳細は here から収集さ、8051 の詳細はhereから収集されました。

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

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

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

次に、R2「スタック」にR3を格納し(R6は一時ストレージに使用されたと思います)、上下に調整し、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 レジスタを呼び出し/復帰に使用します。

于 2009-03-20T02:15:55.267 に答える
24

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);
    }
}
于 2009-03-20T02:51:11.330 に答える
8

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

于 2009-04-19T09:12:26.763 に答える
6

私が見る限り、この点に触れていない他の回答へのほんの少しの追加:

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

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

スタックが上向きに成長した場合、スタックポインタに関連するすべての有用なオフセットは負になり、直感的でなく便利ではなくなります。また、構造体のフィールドへのアクセスなど、レジスタ相対アドレス指定の他のアプリケーションとは相容れません。

于 2019-04-20T10:35:05.220 に答える
5

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

とはいえ、スタックは通常、ほとんどのアーキテクチャで成長します。おそらく、スタックとプログラム データまたはヒープ データが成長して互いに衝突するケースを回避するためです。sh-'s answer に記載されている大きなアドレス指定の理由もあります。いくつかの例: MIPS ABI は下向きに成長し、スタック ポインターとして$29(AKA $sp) を使用します。RISC-V ABI も下向きに成長し、スタック ポインターとして x2 を使用します。

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

https://en.wikipedia.org/wiki/Calling_conventionで、さまざまなアーキテクチャでのスタックの使用に関する詳細情報を見つけることができます。

こちらもご覧ください

于 2013-07-29T13:29:01.583 に答える
2

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

于 2014-01-25T14:23:13.553 に答える
1

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

于 2009-03-20T02:20:20.687 に答える
-1

このマクロは、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;
}
于 2019-02-01T12:35:05.980 に答える