Qual é a direção do crescimento da pilha na maioria dos sistemas modernos?

Estou preparando alguns materiais de treinamento em C e quero que meus exemplos se encaixem no modelo típico de pilha.

Em que direção uma pilha C cresce no Linux, no Windows, no Mac OSX (PPC e x86), no Solaris e nos Unixes mais recentes?

    O crescimento da pilha geralmente não depende do sistema operacional em si, mas do processador em que está sendo executado. O Solaris, por exemplo, é executado no x86 e no SPARC. Mac OSX (como você mencionou) é executado em PPC e x86. O Linux roda em tudo, desde meu grande System z no trabalho até um minúsculo relógio de pulso .

    Se a CPU fornecer qualquer tipo de escolha, a ABI / convenção de chamada usada pelo SO especifica qual opção você precisa fazer se quiser que seu código chame o código de todo mundo.

    Os processadores e sua direção são:

    • x86: para baixo.
    • SPARC: selecionável. A ABI padrão usa para baixo.
    • PPC: para baixo, eu acho.
    • System z: em uma lista encadeada, não estou brincando (mas ainda abaixo, pelo menos para o zLinux).
    • ARM: selecionável, mas Thumb2 tem codificações compactas somente para baixo (LDMIA = incremento após, STMDB = decremento antes).
    • 6502: inativo (mas apenas 256 bytes).
    • RCA 1802A: da maneira que você quiser, sujeito à implementação da SCRT.
    • PDP11: para baixo.
    • 8051: para cima

    Mostrando a minha idade naqueles últimos, o 1802 foi o chip usado para controlar os primeiros shuttles (sentindo se as portas estavam abertas, suspeito, com base no poder de processamento que ele tinha 🙂 e meu segundo computador, o COMX-35 ( seguindo o meu ZX80 ).

    Detalhes PDP11 recolhidos a partir daqui , 8051 detalhes daqui .

    A arquitetura SPARC usa um modelo de registro de janela deslizante. Os detalhes visíveis arquiteturalmente também incluem um buffer circular de janelas de registro que são válidas e armazenadas em cache internamente, com traps quando sobre / underflows. Veja aqui para detalhes. Como explica o manual do SPARCv8, as instruções SAVE e RESTORE são como instruções de ADD, além de rotação de janela de registro. Usar uma constante positiva em vez do negativo normal daria uma pilha crescente.

    A técnica SCRT mencionada anteriormente é outra – o 1802 usou alguns ou dezesseis registradores de 16 bits para SCRT (chamada padrão e técnica de retorno). Um era o contador de programa, você poderia usar qualquer registrador como o PC com a instrução SEP Rn . Um era o ponteiro da pilha e dois eram definidos sempre para apontar para o endereço de código SCRT, um para chamada e outro para retorno. Nenhum registro foi tratado de maneira especial. Tenha em mente que esses detalhes são da memory, eles podem não estar totalmente corretos.

    Por exemplo, se R3 era o PC, R4 era o endereço de chamada SCRT, R5 era o endereço de retorno SCRT e R2 era a “pilha” (aspas como é implementado no software), o SEP R4 como o PC e começaria a funcionar o código de chamada SCRT.

    Ele então armazenaria R3 no R2 “stack” (acho que o R6 era usado para armazenamento temporário), ajustando-o para cima ou para baixo, pegaria os dois bytes seguintes ao R3, carregaria no R3, então faria o SEP R3 e rodaria no novo endereço.

    Para retornar, seria SEP R5 que puxaria o endereço antigo da pilha R2, adicionaria dois a ele (para ignorar os bytes de endereço da chamada), carregaria em R3 e SEP R3 para começar a executar o código anterior.

    É muito difícil envolver a cabeça inicialmente, depois de todo o código baseado em pilha do 6502/6809 / z80, mas ainda assim elegante em uma espécie de “bang-your-head-to-the-wall”. Também um dos grandes resources de venda do chip foi um conjunto completo de 16 registradores de 16 bits, apesar do fato de que você imediatamente perdeu 7 desses (5 para SCRT, dois para DMA e interrupções de memory). Ahh, o triunfo do marketing sobre a realidade 🙂

    O System z é na verdade bastante similar, usando seus registradores R14 e R15 para chamada / retorno.

    Em C ++ (adaptável a 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); } } 

    A vantagem de diminuir é em sistemas mais antigos, a pilha era tipicamente no topo da memory. Normalmente, os programas preenchiam a memory a partir da parte inferior, portanto, esse tipo de gerenciamento de memory minimizava a necessidade de medir e colocar a parte inferior da pilha em algum lugar sensato.

    A pilha cresce em x86 (definida pela arquitetura, ponteiro de pilha de incrementos pop, decrementos por push).

    No MIPS não há instruções push / pop . Todos os pushes / pops são explicitamente feitos por load / store relativamente ao ponteiro da pilha e, em seguida, ajustam manualmente o ponteiro $sp . No entanto, como todos os registradores (exceto $0 ) são de propósito geral, em teoria qualquer registrador pode ser um ponteiro de pilha, e a pilha pode crescer em qualquer direção que o programador desejar. MIPS ABIs tipicamente crescem para baixo.

    Na Intel 8051, a pilha cresce, provavelmente porque o espaço de memory é tão pequeno (128 bytes na versão original) que não há nenhum heap e você não precisa colocar a pilha no topo para que ela fique separada da pilha crescendo Do fundo.

    Ele diminui porque a memory alocada para o programa tem os “dados permanentes”, ou seja, o código para o próprio programa na parte inferior, depois o heap no meio. Você precisa de outro ponto fixo para referenciar a pilha, de modo que você fique no topo. Isso significa que a pilha diminui, até que seja potencialmente adjacente a objects no heap.

    Na maioria dos sistemas, a pilha aumenta e meu artigo em https://gist.github.com/cpq/8598782 explica por que ela diminui. A razão é que é o layout ideal de duas regiões de memory crescente (heap e pilha).