Uso de registro de CPU C++
-
11-09-2019 - |
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++;
. :)
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.