Esta seção descreve quais as informações que devem ser armazenadas nas chamadas de procedimento e por quanto tempo. Para tal, a seção 2.1.1 descreve o escopo das variáveis de procedimentos, a seção 2.1.2 descreve quais informações adicionais devem ser armazenadas e a seção 2.1.3 apresenta a estrutura onde estas informações são armazenadas, o registro de ativação.
2.1.1
Escopo de Variáveis
Na linguagemC, qualquer variável declarada fora dos procedimentos é uma variável global (visíveis em qualquer procedimento) e qualquer variável declarada dentro de um procedimento (local ou parâmetro) só é visível dentro do procedimento.
Em tempo de execução, as variáveis globais são sempre visíveis e as variáveis de um procedimento são alocadas quando o procedimento é chamado e desalocadas quando da saída do procedimento.
Considere um programa onde procedimentoA()chama um procedimentoB(). Em tempo de execução, as variáveis deA()
devem ser alocadas quando ele for chamado. Quando ele chamarB(), as variáveis deB()devem ser alocadas, mas sem apagar as variáveis deA(). Quando sair do procedimentoB(), as variáveis deB()devem ser liberadas, mas sem causar nenhum efeito nas variáveis deA().
Isto fica mais interessante em chamadas recursivas. Considere programa onde procedimentoR()chama a si mesmo recur- sivamente. Em tempo de execução, a primeira chamada aR()provoca a alocação de todas as variáveis deR(). Vamos denominar estas variáveis de VR. Na chamada recursiva deR()(que denominaremos R0()) um novo conjunto de variáveis deve ser alocado, que denominaremos de VR0.
As alterações em VR0não afetam VR. Quando o sair do procedimento R0(), VRcontinua intacto.
2.1.2
Informações de Contexto
As informações de contexto que também devem ser salvas são duas: a que organiza a sequência de chamada e retorno de proce- dimentos, e a que permite acesso às variáveis de um procedimento.
Primeiro iremos descrever a sequencia de chamada e de retorno de procedimentos. Quando se faz a chamada de procedi- mento, sabe-se que o fluxo é desviado para o procedimento. Na saída do procedimento, sabe-se o fluxo deve retornar à instrução seguinte à chamada. A questão é como fazê-lo.
O mecanismo mais simples é utilizar uma pilha. Antes de desviar o fluxo para o procedimento, armazena-se na pilha o en- dereço da instrução seguinte. Para retornar do procedimento de volta ao chamador, utiliza-se o endereço da instrução seguinte à chamada, que está disponível no topo da pilha. Observe que este mecanismo simples permite a chamada de vários procedimen- tos e os respectivos retornos sem haver confusão.
O passo seguinte é descrever o acesso às variáveis. Para entender o problema, considere mais uma vez um programa onde procedimentoR()chama a si mesmo recursivamente. Na primeira chamada, as variáveis deR(), que chamamos de VRdevem ser alocadas e podem ser acessadas. Seja vruma variável declarada em VR. Quando o procedimentoR()acessar vr, a instância a ser usada é vr∈ VR.
Agora considere as chamadas recursivas deR()que denominaremos R0(), R00(), e assim por diante. Sejam VR0as variáveis de
R0(),VR00as variáveis de R00(), e assim por diante.
Agora considere que o procedimentoR()acessa vr. Existem dois vr: o primeiro é vr∈ VRe o segundo é vr∈ VR0, sendo que
VR6= VR0. Evidentemente o correto é acessar vr∈ VR0, a última alocada.
Vamos agora nos concentrar no que ocorre quando o procedimento R0() finalizar. O espaço alocado para V
R0deve ser liberado
e o fluxo retorna à instrução seguinte à chamada, dentro deR(). Neste ponto considere queR()volta a acessar vr. Neste caso, deve acessar vr∈ VR.
Para atingir este objetivo, uma solução é usar uma variável global que indica qual o conjunto de variáveis deve ser usada a cada momento (no exemplo, VRou VR0). Seja rbaseesta variável.
Assim como no caso do endereço de retorno, é necessário salvar o valor corrente de rbaseantes de alterar o seu valor. Nova- mente, uma pilha é uma estrutura adequada.
Voltando ao exemplo, considere que emR(), temos rbase→ VR2. Quando acessar vr, usaremos vr[rbase] (ou seja, vr∈ VR). Ao chamarR()recursivamente, salvamos rbasena pilha e o substituímos por rbase→ VR0. Observe que o acesso a vré exatamente igual, vr[rbase], só que agora vr∈ VR0.
Ao retornar do procedimento, restaura-se o valor de rbasedo topo da pilha, ou seja, rbase→ VR. O acesso a vrcontinua igual: vr[rbase], só que agora vr∈ VR.
2 lê-se r
2.1. Informações Armazenadas nas Chamadas de Procedimento 31
Normalmente, a variável rbaseé armazenada em um registrador (que só é usado para isso) para ganhar desempenho. Este registrador recebe nomes diferentes dependendo da CPU. Por exemplo, no AMD64 ele é chamado base register, registrador de base (%rbp) enquanto no MIPS ele é chamado frame pointer (fp).
Apesar de tecnicamente correta, a explicação acima é muito complicada para ser entendida por iniciantes. Por esta razão, as próximas 15 ou 20 páginas detalham na prática o que se explicou nas últimas 35 linhas.
2.1.3
Registro de Ativação
“Registro de Ativação” é o termo adotado para indicar a estrutura criada em tempo de execução para armazenar: 1. variáveis de um procedimento: a) variáveis locais; b) parâmetros; 2. informações de contexto: a) endereço de retorno; b) registrador de base.
Conforme explicado nas seções 2.1.1 e 2.1.2, todas estas informações devem ser armazenadas em uma pilha.
Por esta razão, o registro de ativação também funciona como uma pilha. O local específico onde ela é alocada e liberada está na região stack3. Veja a região destacada na figura 4).
O modelo esquemático de um registro de ativação é apresentado na figura 5.
0x0000080000000000
0x0000000000000000
Parâmetros
Informações
de
Contexto
Variáveis
Locais
6
Crescimento
da
Pilha
r
baseFigura 5 – Registro de ativação: modelo esquemático
A ordem em que as informações são armazenadas é importante. Para entender porquê, considere uma chamada de procedi- mento, digamosP(p1,p2).
1. Antes de desviar para o procedimentoP, empilham-se os parâmetros (p1ep2).
2. Executa-se a chamada de procedimento salvando o endereço de retorno na pilha (na área das informações de contexto). 3. Salva-se o valor corrente do registrador de base (rbase), e substitui o valor de rbasepara que ele aponte para as variáveis
do registro de ativação corrente.
4. Por fim, aloca espaço para as variáveis locais. 3 Não coincidentemente stack significa pilha em português
O registrador de base (rbase) aponta sempre para um mesmo local no registro de ativação (mas não para o mesmo ende- reço de memória). Como o a quantidade de informações de contexto é fixa, basta somar uma constante a rbasepara acessar as variáveis locais, e basta subtrair uma constante para acessar os parâmetros.
Vamos detalhar melhor o que significa dizer que os registros de ativação seguem o modelo de pilha com um exemplo. Quando ocorre a chamada de um procedimentoA(), o registro de ativação deA(), digamos R AA()é empilhado, e fica no topo da pilha. SeA()chamar um procedimentoB(), um novo registro de ativação paraB(), digamos R AB ()é empilhado sobre R AA()e R AB ()é que estará no topo da pilha. QuandoB()finalizar, R AB ()é desempilhado e R AA()estará no topo da pilha. É importante destacar que rbasesempre aponta para o registro de ativação do topo da pilha.