Compreendendo o quadro de pilha da chamada de função em C / C ++?

15

Eu estou tentando entender como os quadros de pilha são construídos e quais variáveis (params) são empurradas para empilhar em que ordem? Alguns resultados de pesquisa mostraram que o compilador C / C ++ decide com base nas operações realizadas em uma função. Por exemplo, se a função deveria apenas incrementar um valor int passado em 1 (semelhante ao operador ++) e retorná-lo, ele colocaria todos os parâmetros da função e variáveis locais em registradores.

Eu estou querendo saber quais registros são usados para os parâmetros retornados ou passar por valor. Como as referências são retornadas? Como o compilador escolhe entre eax, ebx, ecx e edx?

O que eu preciso saber para entender como registradores, pilha e referências de heap são usados, construídos e destruídos durante as chamadas de função?

    
por Gana 18.04.2013 / 18:58
fonte

4 respostas

8

Além do que Dirk disse, um uso importante de quadros de pilha é salvar valores anteriores de registros para que eles possam ser restaurados após uma chamada de função. Portanto, mesmo em processadores nos quais os registradores são usados para passar parâmetros, retornar um valor e salvar o endereço de retorno, os valores desses registros são salvos na pilha antes de uma chamada de função, para que possam ser restaurados após a chamada. Isso permite que uma função chame outra sem sobrescrever seus próprios parâmetros ou esquecer seu próprio endereço de retorno.

Portanto, chamar uma função B da função A em um sistema "genérico" típico pode envolver as seguintes etapas:

  • função A:
    • empurrar espaço para o valor de retorno
    • parâmetros de envio
    • envie o endereço de retorno
  • pula para a função B
  • função B:
    • envie o endereço do quadro de pilha anterior
    • envia valores de registros usados por essa função (para que possam ser restaurados)
    • empurrar espaço para variáveis locais
    • faça o cálculo necessário
    • restaura os registros
    • restaura o quadro da pilha anterior
    • armazena o resultado da função
    • pule para o endereço de retorno
  • função A:
    • pop os parâmetros
    • pop o valor de retorno

Esta não é a única maneira que as chamadas de função podem funcionar (e eu posso ter uma ou duas etapas fora de ordem), mas deve dar uma idéia de como a pilha é usada para permitir que o processador manipule chamadas de função aninhadas .

    
por 18.04.2013 / 23:50
fonte
9

Isso depende da convenção de chamada que está sendo usada. Quem define a convenção de chamada pode tomar essa decisão como quiser.

Na convenção de chamada mais comum no x86, os registros não são usados para passar parâmetros; os parâmetros são colocados na pilha, começando pelo parâmetro mais à direita. O valor de retorno é colocado em eax e pode usar edx se precisar de espaço extra. Referências e ponteiros são retornados na forma de um endereço no eax.

    
por 18.04.2013 / 19:25
fonte
4

Se você entender muito bem a pilha, entenderá como a memória funciona no programa e, se entender como a memória funciona no programa, entenderá como armazenar as funções no programa e entender o armazenamento das funções no programa. funciona e se você entender como funciona a função recursiva, você entenderá como o compilador funciona e, se entender como o compilador funciona, sua mente funcionará como compilador e você depurará qualquer programa com muita facilidade

Deixe-me explicar como a pilha funciona:

Primeiro, você precisa saber como armazenar funções na pilha:

Heap armazena valores de alocação de memória dinâmica. Valores de alocação e exclusão automáticos de armazenamento de pilha.

Vamosentendercomoexemplo:

defhello(x):ifx==1:return"op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Agora, entenda partes deste programa:

Agoravamosveroqueépilhaeoquesãopartesdapilha:

Allocationofthestack:

Lembre-sedeumacoisasequalquerfunçãoreceber"retorno", não importando se carregou todos os seus variados locais ou qualquer coisa que retornará imediatamente da pilha será o seu stack frame. Significa que quando qualquer função recursiva obtém a condição base e colocamos a condição de retorno após base para que a condição base não espere para carregar variáveis locais que estão situadas na parte “else” do programa ele retornará imediatamente o quadro atual da pilha e agora se um quadro return next frame está no registro de ativação. Veja isto na prática:

Deallocationoftheblock:

Então,agora,semprequeumafunçãoencontrouadeclaraçãoderetorno,elaexcluioquadroatualdapilha.

aoretornardovalordapilharetornaránaordeminversadaordememqueelesalocaramnapilha.

Estassãodescriçõesmuitocurtasesevocêquisersabermaissobrestackedoublerecursionleiadoispostsdesteblog:

Mais sobre pilha passo a passo

Mais sobre a dupla recursão passo a passo com a pilha

    
por 18.10.2016 / 13:05
fonte
3

O que você está procurando é chamado Interface Binária do Aplicativo - ABI.

Existe uma especificação para cada compilador que soletra a ABI.

Cada plataforma normalmente especificará e ABI para suportar a interoperabilidade entre os compiladores. Por exemplo, Convenções de Chamada x86 explicita as convenções de chamada típicas para x86 e x86-64. Eu esperaria um documento mais oficial do que a wikipedia, no entanto.

    
por 19.04.2013 / 03:56
fonte