OAK CODERS - Home Page
root@oakcoders:~# Dear Visitor, please leave a comment!
"When I read commentary about suggestions for where C should go, I often think back
and give thanks that it wasn't developed under the advice of a worldwide crowd." Dennis Ritchie.

Saturday, February 18, 2012

Uma abordagem 'geral' sobre Stack.

 Antes de ir para o tema principal, eu irei abordar alguns conceitos simples que será fundamental para obter um aprendizado eficiente do conteúdo aqui contido.

COMO AS FUNÇÕES TRABALHAM
 Uma função é composta de diversas peças fundamentais para o seu funcionamento.

- Function Name: é um símbolo que representa um endereço onde o código da função inicia.

- Parameters: são dados explícitamente fornecidos para a função realizar o seu trabalho.

- Local Variables: são dados utilizados internamente pela função.

- Return Address: é um parâmetro 'invisível' que não é utilizado diretamente pela função. O return address comunica a função onde ela deve voltar depois de ter finalizado o seu trabalho. Na maioria das linguagens de programação esse parâmetro é passado automaticamente quando uma função é chamada.

- Return Value: é um método utilizado para retornar um valor. Na maioria das linguagens apenas um valor é permitido como retorno.
ex: Função x chama função y; função y retorna um valor para x.
obs: A linguagem C armazena o valor de retorno no registrador eax.

 Lembrando que é impossível generalizar como as funções são chamadas, como parâmetros são enviados ou como as funções retornam valores, pois isso varia de linguagem para linguagem.
A forma que os dados são transferidos entre funções é conhecida por calling convention.

 Vamos falar agora sobre o que realmente interessa, como a stack memory funciona.
 Utilizando uma analogia bem conhecida, pense na stack como uma pilha de papeís sobre a sua mesa, as coisas que são importantes ficam sempre no topo, porém o que não é utilizado será jogado fora.

A stack 'funciona' como uma pilha de papeís.

É importante ressaltar que a stack trabalha utilizando o método LIFO (Last In, First Out), ou seja, o último dado a ser armazenado será o primeiro a sair. Tudo que conhecemos por parâmetros e variáveis locais será armazenado lá.
Lembrando que cada programa de computador tem sua própria stack, para mais informações pesquise sobre 'Program Memory'.

Programas de computadores trabalham colocando dados no topo da stack e quando eles não são mais necessários vão sendo 'retirados'.
Embora possa parecer confuso, a stack inicia-se em um endereço de memória x e vai crescendo para baixo. Qualquer referência ao topo da stack, lembre-se disso.

 Antes de chamar uma função, um programa coloca na pilha em ordem reversa todos os parâmetros documentados[1] e utiliza a instrução call que faz duas coisas: Primeiro ela coloca o endereço da próxima instrução na pilha, vulgo 'return address'; Então ela modifica o registrador instruction pointer(%eip) para apontar para o início da função.

[1]: Ex: function(parameter1, parameter2).

Dessa forma, segue abaixo uma ilustração de como a stack irá ficar:
         High Address

             |                ...
             |              Parameter 2
             |              Parameter 1
             V              Return address <-- (Topo da pilha)

         Low Address                                                   

obs: O topo da pilha é marcado pelo registrador %esp.
 Quando uma função é chamada, ela 'automaticamente' salva o topo da pilha(%esp) no registrador base pointer(%ebp). Isso é totalmente necessário para saber onde as coisas encontram-se na stack, seja variáveis locais, parâmetros, etc.

 Entendendo melhor: se um novo valor é inserido na stack, o topo irá mover. Com base nisso, seria impossível saber onde as coisas estão.
O 'base pointer' (%ebp) pode ser considerado uma espécie de referência para a stack frame[1].

[1]: stack frame é uma area na stack que contém todos os dados utilizados pela função(parâmetros, variáveis locais, return address,...).

Neste ponto, a stack parecerá com isso:
         High Address

             |                ...
             |              Parameter 2
             |              Parameter 1
             |              Return address
             V              %ebp value <-- (Topo da pilha)

         Low Address                                                   

 Vamos supor que essa atual função(x) queira chamar a função(y), então ela armazenará na stack os parâmetros necessários para chamar a função y.
         High Address

             |                ...
             |              Parameter 2
             |              Parameter 1
             |              Return address
             |              %ebp value 
             V              Parâmetro para função y. (Topo da pilha)
                           
         Low Address                                                 

obs: o parâmetro para função y é uma variável local. Note também que ao movermos o topo da pilha(%esp), se não tivessemos armazenado o topo no início da função em (%ebp) seria impossível saber onde encontram-se os dados na stack frame. Podemos então concluir que o (%ebp) é utilizado como referência pelas funções.

- Quando a função é finalmente executada, acontece 4 coisas:
1. O valor de retorno é armazenado no registrador %eax.
2. Então colocamos o valor do registrador %ebp em %esp, dessa forma o topo da pilha será reiniciado para quando ela foi chamada.
         High Address

             |                ...
             |              Parameter 2
             |              Parameter 1
             |              Return address 
             |              %ebp value (Topo da pilha)
             V              Parâmetro para função y. 
                           
         Low Address                                                    

3. Ela 'retira' o valor que encontra-se no topo, no caso o %ebp value.
         High Address

             |                ...
             |              Parameter 2
             |              Parameter 1
             |              Return address (Topo da pilha)
             |              %ebp value 
             V              Parâmetro para função y. 
                           
         Low Address                                                    

obs: Como vocês podem notar, os valores não são removidos, apenas o topo da pilha é movido.

4. E para finalizar, independente do valor que está no topo da pilha, ele é colocado no registrador %eip (O registrador %eip aponta para a próxima instrução a ser executada).

Após a chamada da função, geralmente move-se o topo da pilha para acima dos parâmetros colocados na stack, segue a ilustração:
         High Address
                            ---Função que chamou x (main)---
             |              Return address 
             |              Local Variable
             |              Local Variable (Topo da pilha)
                            ---Função x---
             |              Parameter 2
             |              Parameter 1
             |              Return address 
             |              %ebp value 
             V              Parâmetro para função y. 
                           
         Low Address                                                 

Dessa forma, é interessante notar que todos os valores armazenados para a função x ainda encontram-se na stack, só que futuramente novas inserções irá sobreescrever os mesmos.

Será que isso é verdade? Eu lhe provo isso em código.
Se isso for verdade nós apenas precisaremos de salvar o endereço da variável local da stack em um ponteiro e posteriormente gravar em outra variável para que o valor não seja sobreescrito por outra stack frame.

/* -- Segue o código: -- */
int *p;

void function(void)
{
 int i = 3;
 p = &i;
}

main ()
{
 int i = 0;
 function();
 i = *p;

 printf("%d", i);
 return 0;
}

O resultado disso, foi exatamente o esperado: 3.

Gostaria de deixar claro que certas informações aqui não podem ser generalizadas, pois as implementações em chamadas de funções podem variar de linguagem para linguagem (calling convention).

Espero que todos os leitores possam tirar bom proveito da informações. Qualquer informação ou correção, favor me enviar um e-mail.
---------------------
Regards, Utroz.
E-mail Contact: utroz (at) oakcoders (dot) com

1 comment:

  1. parabens utroz seu blog esta se tornando um dos melhores de linguagem C em pt-br... material de grande qualidade ...

    ReplyDelete