スタックの動作(成長または成長)は、アプリケーションバイナリインターフェイス(ABI)と、呼び出しスタック(別名アクティベーションレコード)の編成方法によって異なります。
プログラムは、その存続期間を通じて、OSなどの他のプログラムと通信する必要があります。ABIは、プログラムが別のプログラムと通信する方法を決定します。
異なるアーキテクチャのスタックはどちらの方法でも拡張できますが、アーキテクチャの場合は一貫性があります。このwikiリンクを確認してください。ただし、スタックの成長は、そのアーキテクチャのABIによって決定されます。
たとえば、MIPS ABIを使用する場合、コールスタックは次のように定義されます。
関数「fn1」が「fn2」を呼び出すと考えてみましょう。これで、「fn2」で見られるスタックフレームは次のようになります。
direction of | |
growth of +---------------------------------+
stack | Parameters passed by fn1(caller)|
from higher addr.| |
to lower addr. | Direction of growth is opposite |
| | to direction of stack growth |
| +---------------------------------+ <-- SP on entry to fn2
| | Return address from fn2(callee) |
V +---------------------------------+
| Callee saved registers being |
| used in the callee function |
+---------------------------------+
| Local variables of fn2 |
|(Direction of growth of frame is |
| same as direction of growth of |
| stack) |
+---------------------------------+
| Arguments to functions called |
| by fn2 |
+---------------------------------+ <- Current SP after stack
frame is allocated
これで、スタックが下向きに成長することがわかります。したがって、変数が関数のローカルフレームに割り当てられている場合、変数のアドレスは実際には下向きに大きくなります。コンパイラは、メモリ割り当ての変数の順序を決定できます。(あなたの場合、最初にスタックメモリが割り当てられるのは「q」または「s」のいずれかです。ただし、通常、コンパイラは変数の宣言の順序に従ってスタックメモリの割り当てを行います)。
ただし、配列の場合、割り当てには単一のポインタしかなく、割り当てる必要のあるメモリは実際には単一のポインタによってポイントされます。配列のメモリは連続している必要があります。したがって、スタックは下向きに成長しますが、アレイの場合、スタックは成長します。