Pergunta

Em C++, as variáveis ​​locais são sempre alocadas na pilha.A pilha faz parte da memória permitida que seu aplicativo pode ocupar.Essa memória é mantida na sua RAM (se não for transferida para o disco).Agora, um compilador C++ sempre cria código assembler que armazena variáveis ​​locais na pilha?

Tomemos, por exemplo, o seguinte código simples:

int foo( int n ) {
   return ++n;
}

No código assembler do MIPS, isso poderia ser assim:

foo:
addi $v0, $a0, 1
jr $ra

Como você pode ver, não precisei usar a pilha para n.O compilador C++ reconheceria isso e usaria diretamente os registros da CPU?

Editar: Uau, muito obrigado por suas respostas quase imediatas e extensas!O corpo da função foo deve, obviamente, ser return ++n;, não return n++;. :)

Foi útil?

Solução

Isenção de responsabilidade:Não conheço MIPS, mas conheço alguns x86 e acho que o princípio deveria ser o mesmo.

Na convenção usual de chamada de função, o compilador enviará o valor de n na pilha para passá-lo para a função foo.Contudo, existe o fastcall convenção que você pode usar para dizer ao gcc para passar o valor através dos registradores.(MSVC também tem essa opção, mas não tenho certeza de qual é sua sintaxe.)

test.cpp:

int foo1 (int n) { return ++n; }
int foo2 (int n) __attribute__((fastcall));
int foo2 (int n) {
    return ++n;
}

Compilando o acima com g++ -O3 -fomit-frame-pointer -c test.cpp, eu recebo por foo1:

mov eax,DWORD PTR [esp+0x4]
add eax,0x1
ret

Como você pode ver, ele lê o valor da pilha.

E aqui está foo2:

lea eax,[ecx+0x1]
ret

Agora ele pega o valor diretamente do cadastro.

É claro que, se você incorporar a função, o compilador fará uma adição simples no corpo da sua função maior, independentemente da convenção de chamada especificada.Mas quando você não consegue incorporá-lo, isso vai acontecer.

Isenção de responsabilidade 2:Não estou dizendo que você deva questionar continuamente o compilador.Provavelmente não é prático e necessário na maioria dos casos.Mas não presuma que isso produz código perfeito.

Editar 1: Se você está falando sobre variáveis ​​locais simples (não argumentos de função), então sim, o compilador irá alocá-las nos registros ou na pilha conforme achar adequado.

Editar 2: Parece que a convenção de chamada é específica da arquitetura e o MIPS passará os primeiros quatro argumentos da pilha, como Richard Pennington afirmou em sua resposta.Portanto, no seu caso, você não precisa especificar o atributo extra (que é na verdade um atributo específico do x86).

Outras dicas

Sim.Não existe uma regra de que "as variáveis ​​​​são sempre alocadas na pilha".O padrão C++ não diz nada sobre uma pilha. Ele não pressupõe que exista uma pilha ou que existam registros.Apenas diz como o código deve se comportar, não como deve ser implementado.

O compilador apenas armazena variáveis ​​na pilha quando necessário - quando elas precisam passar por uma chamada de função, por exemplo, ou se você tentar obter o endereço delas.

O compilador não é estúpido.;)

Sim, um bom C/C++ otimizado irá otimizar isso.E até mesmo MUITO mais: Veja aqui:Pesquisa do compilador Felix von Leitners.

De qualquer maneira, um compilador C/C++ normal não colocará todas as variáveis ​​na pilha.O problema com o seu foo() A função pode ser que a variável possa ser passada através da pilha para a função (a ABI do seu sistema (hardware/SO) define isso).

Com C register palavra-chave você pode dar ao compilador uma dica que provavelmente seria bom armazenar uma variável em um registrador.Amostra:

register int x = 10;

Mas lembre-se:O compilador é livre para não armazenar x em um cadastro se quiser!

A resposta é sim, talvez.Depende do compilador, do nível de otimização e do processador alvo.

No caso dos mips, os primeiros quatro parâmetros, se pequenos, são passados ​​em registradores e o valor de retorno é retornado em um registrador.Portanto, seu exemplo não exige alocação de nada na pilha.

Na verdade, a verdade é mais estranha que a ficção.No seu caso, o parâmetro é retornado inalterado:o valor retornado é o de n antes do operador ++:

foo:
    .frame  $sp,0,$ra
    .mask   0x00000000,0
    .fmask  0x00000000,0

    addu    $2, $zero, $4
    jr      $ra
    nop

Desde o seu exemplo foo function é uma função de identidade (ela apenas retorna seu argumento), meu compilador C++ (VS 2008) remove completamente essa chamada de função.Se eu mudar para:

int foo( int n ) {
   return ++n;
}

o compilador inline isso com

lea edx, [eax+1] 

Sim, os registros são usados ​​em C++.O MDR (registros de dados de memória) contém os dados que estão sendo buscados e armazenados.Por exemplo, para recuperar o conteúdo da célula 123, carregaríamos o valor 123 (em binário) no MAR e realizaríamos uma operação de busca.Quando a operação for concluída, uma cópia do conteúdo da célula 123 estará no MDR.Para armazenar o valor 98 na célula 4, carregamos 4 no MAR e 98 no MDR e realizamos um armazenamento.Quando a operação for concluída, o conteúdo da célula 4 terá sido definido como 98, descartando o que estava lá anteriormente.Os registros de dados e endereços trabalham com eles para conseguir isso.Também em C++, quando inicializamos uma var com um valor ou perguntamos seu valor, o mesmo fenômeno acontece.

E, mais uma coisa, os compiladores modernos também realizam alocação de registros, que é um pouco mais rápida que a alocação de memória.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top