Lição
A teoria — A pilha de chamadas: registos de ativação, endereços de retorno e transbordamentos
A pilha de chamadas é uma região de memória onde cada chamada de função recebe um registo de ativação. Aqui, esse registo contém o argumento, o endereço de retorno (o ponto onde a função chamadora retoma a execução), o ponteiro de registo de ativação da função chamadora, guardado para posterior reposição, e as variáveis locais. O ponteiro da pilha indica o endereço mais baixo em uso. Uma chamada fá-lo descer o espaço de um registo; o retorno fá-lo subir de novo. Uma recursão com d chamadas de profundidade e registos de f bytes exige d·f bytes de pilha.
O que significa cada símbolo
d- profundidade: registos de ativação simultaneamente na pilha
f- bytes por registo de ativação: aqui, 12 mais 4 por variável local
- Pressupõe
- Uma máquina de 32 bits com a convenção clássica de chamadas em C: posições de 4 bytes, pilha a crescer para endereços mais baixos, argumentos passados pela pilha e um ponteiro de registo de ativação em cada registo. Nas convenções de 64 bits usadas na prática, os primeiros argumentos passam por registos do processador. Os compiladores podem omitir o ponteiro de registo de ativação e, em ARM, uma função que não chama outras pode manter o endereço de retorno num registo do processador. Por isso, o tamanho e a disposição dos registos de ativação reais diferem deste modelo de três posições.
Problema resolvido na íntegra
-
Até que profundidade pode ir a recursão? 4 passos
Carregue em fact(11) transborda. A pilha desta página tem 128 bytes. Por predefinição, o editor de ligações da Microsoft reserva 1 MB de pilha para um programa. Considere esse valor como 1 MiB, ou 1 048 576 bytes, com os registos de ativação usados nesta página. Até que profundidade pode ir fact? E se cada chamada tiver também uma variável local de 4 bytes? Compare ainda a memória de pilha necessária para fib(30) com o número de chamadas que faz.
Abrir este problema na ferramenta
-
Aqui, cada registo de ativação contém um argumento, um endereço de retorno e um ponteiro de registo de ativação guardado: 3 × 4 = 12 bytes. Como 128 / 12 = 10,67, cabem 10 registos. fact(11) exige 11 × 12 = 132 bytes. A tabela de resultados indica o transbordamento.
-
1 048 576 / 12 = 87 381,33, pelo que cabem 87 381 registos de ativação. fact poderia atingir 87 381 níveis de recursão antes de esgotar a pilha. Muito antes disso, o resultado já deixaria de caber num int de 32 bits: isso acontece em 13! = 6 227 020 800.
-
Com uma variável local de 4 bytes, cada registo de ativação ocupa 16 bytes, e 1 048 576 / 16 = 65 536, exatamente.
-
A cadeia mais longa de fib(30) tem 30 registos de ativação: 30 × 12 = 360 bytes. O número de chamadas é 2·F(31) − 1, com F(31) = 1 346 269, o 31.º número de Fibonacci. São 2 692 537 chamadas.
Resposta
87 381 níveis, ou 65 536 com uma variável local. fib(30) precisa de 360 bytes de pilha para 2 692 537 chamadas. A profundidade determina a memória necessária na pilha; em fib, o número de chamadas determina o tempo de execução.
-
Percurso de aprendizagem
Como um computador executa um programa
Referências (2)
- The worked problem's stack size: the linker's default reservation: Microsoft Learn, "/STACK (Stack allocations)," C/C++ build reference, updated 23 February 2022.
- The first insight's 1988 example: fingerd's unbounded read into a 512-byte buffer on the stack: Bob Page, "A Report on the Internet Worm." University of Lowell, 7 November 1988 (copy hosted at Toronto Metropolitan University).