Entendendo a “Stack” em sua forma mais primitiva (em Assembly)

Elemar Júnior

Conhecer assembly, geralmente, não é fundamental para o dia a dia de um programador. Entretanto, entender como assembly funciona ajuda a valorizar determinadas características de linguagens de programação de nível mais alto e, até mesmo, deixar mais confortável cenários mais complexos de depuração ou otimização.

Um dos conceitos centrais de programação em assembly que ajudam a entender o comportamento do código, em uma linguagem como Java ou C#, é a stack

O que é a Stack?

Trata-se de uma área contínua de memória que nossos programas utilizam para manipular dados, principalmente primitivos, e é intensamente utilizada na “comunicação” durante chamadas e retornos de funções.

Os dados são “empilhados” na stack usando, em assembly, a instrução push e são desempilhados usando a instrução pop. O endereço de memória correspondente ao “topo” da stack é mantindo no registrador esp.

O topo da pilha “cresce negativamente”, de high-memory para low-memory. Assim, sempre que um valor é empilhado na stack, o valor de esp é decrescido. De maneira análoga, sempre que um valor é “desempilhado” da stack, o valor de esp é acrescido.

Qual a relação entre a Stack e a execução de funções?

De maneira geral, sempre que uma função é chamada, os dados necessários para sua execução são dispostos na stack obedecendo uma determinada convenção. Voltando ao código do post anterior,  podemos perceber no “header” da função addInAsm a convenção que deveria ser respeitada (cdecl).

#include <iostream>

extern "C" int addInAsm(int a, int b);

int main() {
	int a = 2;
	int b = 3;

	int result = addInAsm(a, b);

	std::cout << a << " + " << b << " = " << result << std::endl;
	return 0;
}

O código em assembly que escrevemos utiliza “empiricamente” o que está acordado na convenção para acessar os dados.

    .model flat, c
    .code

addInAsm proc 

; Initialize a stack frame pointer  
    push ebp
    mov  ebp, esp

; load the paramaters 
    mov  eax, [ebp + 8]    ; eax = a
    mov  ecx, [ebp + 12]   ; ecx = b

;
    add  eax, ecx         

; restore the stack frame
    pop  ebp
    ret

addInAsm endp

    end

Segundo as convenções, a stack foi atualizada, na chamada, para conter, os parâmetros da função e o endereço de retorno para quando a função encerrar.

Perceba que códigos em assembly não empregam sistemas sofisticados de tipos. Ou seja, não há abstrações com relação a valores em memória – tudo são bytes que ganham significado conforme a intenção do código e são acessados através dos deslocamentos impostos pelo “tamanho” de cada dado.

O endereço de retorno é capturado pela instrução ret, diretamente da stack, para saber onde está a próxima instrução da função chamadora (main) a ser procesada.

Qual a relação entre a Stack e a parâmetros “byref”?

Quando passamos parâmetros “por referência”, mandamos na Stack, no lugar dos valores, os endereços de memória correspondendo as variáveis que desejamos atualizar.

#include <iostream>

extern "C" void addMul(int a, int b, int* sum, int* prod);

int main() {
	int a = 2;
	int b = 5;
	int sum = 0;
	int prod = 0;

	addMul(a, b, &sum, &prod);

	std::cout <<
		"The sum of "
		<< a <<
		" and "
		<< b <<
		" is "
		<< sum << " and the product is " << prod
		<< std::endl;
}

No exemplo, os parâmetros prod e sum são passados como referência, mas a estrutura na stack é praticamente inalterada.

addMul proc

        push ebp
        mov ebp,esp

        push edx
        push ecx
        push eax

        mov ecx,[ebp+8]                     ;ecx = 'a'
        mov eax, ecx                        ;eax = 'a'
        mov edx,[ebp+12]                    ;edx = 'b'

        imul ecx,edx                        ;edx = 'a' * 'b'
        mov ebx,[ebp+20]                    ;ebx = 'prod'
        mov [ebx],ecx                       ;save product

        add eax, edx                        ;eax = 'a' + 'b'
        mov ebx,[ebp+16]                    ;ebx = 'sum'
        mov [ebx],eax                       ;save sum

        pop eax
        pop ecx
        pop edx
        
        pop ebp

        ret
addMul endp
        end

A mudança na stack fica apenas nas duas novas posições necessárias para acomodar os novos valores.

No código em assembly, repare que utilizemos os “endereços” contidos nos parâmetros e não seus valores diretamente (como fazemos para as variáveis a e b).

O que acontece se a memória destinada para a Stack for esgotada?

O resultado depende do ambiente operacional onde estamos trabalhando. Em C#, por exemplo, uma StackOverflowException irá ser disparada e o programa se encerrará.

Importante indicar que o “esgotamento” da Stack geralmente é causado por execuções recursivas em demasia. Afinal, a stack é utilizada para fazer “o caminho de volta” na execução de diversas funções.

Eventualmente, podemos escrever código que ajuda o compilador a entender que “não será necessário” voltar para a função quando houver um retorno.

int factorial(int n, int b = 1) {
    if (n == 0) {
        return b;
        }
    return factorial(n - 1, b * n);
}

Alguns compiladores conseguem identificar esses cenários e não criar um registro na stack para cada chamada.

Concluindo

A stack é um dos conceitos fundamentais para execução de programas de computador em qualquer ambiente moderno. Em seu formato mais “bruto” trata-se apenas de um espaço contínuo de memória que é atualizado seguindo algumas convenções muito simples.

O conhecimento sobre como a Stack é manipulada, em seu estado mais fundamental, ajuda programadores a apreciar o bom trabalho dos compiladores e entender oportunidades de otimização.

Em resumo

O fato
Saber assembly não é fundamental. Entretanto, é importante que todos saibamos que programas modernos, mesmo os escritos em linguagens mais sofisticadas, em sua execução utilizam uma implementação primitiva de Stack – na prática, uma área contínua de memória utilizada na “comunicação” que ocorre na chamada e no retorno das funções.
O insight
Conhecer como a “stack” é utilizada, escrevendo algum código em assembly, ajuda a entender como a dinâmica de execução das aplicações modernas acontece. Eventualmente, ajuda a estruturar programas para que eles sejam mais eficientes.

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
1 Comentário
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Sérgio Trovatti Uetanabaro
Sérgio Trovatti Uetanabaro
2 anos atrás

Bem didático esse artigo mas tem uma observação:

imul ecx,edx ;edx = ‘a’ * ‘b’

ECX receberia o acumulado, não EDX: ecx = ‘a’ * ‘b’

AUTOR

Elemar Júnior
Fundador e CEO da EximiaCo atua como tech trusted advisor ajudando empresas e profissionais a gerar mais resultados através da tecnologia.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Arquitetura de Dados

Insights de um DBA na análise de um plano de execução

Especialista em performance de Bancos de Dados de larga escala
Arquitetura de Software

Estratégias para modernização do legado

Desenvolvedor .NET/NodeJs e especialista em Kafka com experiência em startups e grandes empresas
Infraestrutura e Nuvem

Migração para a nuvem, mais do que mudança tecnológica, implica em mudança da cultura organizacional

Engenheiro de nuvem, arquiteto de software e especialista em Containers e Devops

Acesse nossos canais

Simplificamos, potencializamos e aceleramos resultados usando a tecnologia do jeito certo

EximiaCo 2022 – Todos os direitos reservados

1
0
Queremos saber a sua opinião, deixe seu comentáriox
()
x

Entendendo a “Stack” em sua forma mais primitiva (em Assembly)

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?