O comportamento da pilha (crescendo ou diminuindo) depende da interface binária do aplicativo (ABI) e de como a pilha de chamadas (também conhecida como registro de ativação) está organizada.
Ao longo de sua vida, um programa é obrigado a se comunicar com outros programas, como o sistema operacional. ABI determina como um programa pode se comunicar com outro programa.
A pilha para diferentes arquiteturas pode crescer de qualquer maneira, mas para uma arquitetura será consistente. Por favor, verifique este link do wiki. Porém, o crescimento da pilha é decidido pela ABI dessa arquitetura.
Por exemplo, se você pegar o MIPS ABI, a pilha de chamadas é definida conforme abaixo.
Vamos considerar que a função 'fn1' chama 'fn2'. Agora, o frame da pilha visto por 'fn2' é o seguinte:
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
Agora você pode ver que a pilha cresce para baixo. Portanto, se as variáveis são alocadas ao quadro local da função, os endereços das variáveis na verdade crescem para baixo. O compilador pode decidir sobre a ordem das variáveis para alocação de memória. (No seu caso, pode ser 'q' ou 's' que é a primeira alocação de memória de pilha. Mas, geralmente o compilador faz a alocação de memória de pilha de acordo com a ordem de declaração das variáveis).
Mas no caso dos arrays, a alocação possui apenas um único ponteiro e a memória que precisa ser alocada será na verdade apontada por um único ponteiro. A memória precisa ser contígua para uma matriz. Portanto, embora a pilha cresça para baixo, para os arrays, a pilha aumenta.