qual será o modo de endereçamento no código assembly gerado pelo compilador aqui?
-
26-09-2019 - |
Pergunta
Suponha que temos duas variáveis inteiras e de caracteres:
int adad=12345;
char character;
Supondo que estamos discutindo uma plataforma na qual o comprimento de uma variável inteira é maior ou igual a três bytes, quero acessar o terceiro byte desse número inteiro e colocá-lo na variável de caractere, com isso dito eu escreveria como esse:
character=*((char *)(&adad)+2);
Considerando essa linha de código e o fato de não ser um compilador ou especialista em assembly, sei um pouco sobre como abordar modos em assembly e estou me perguntando o endereço do terceiro byte (ou acho que é melhor dizer deslocamento do terceiro byte) aqui estaria dentro das instruções geradas pela própria linha de código ou estaria em uma variável separada cujo endereço (ou desvio) está dentro dessas instruções?
Solução
A melhor coisa a fazer em situações como essa é tentar.Aqui está um exemplo de programa:
int main(int argc, char **argv)
{
int adad=12345;
volatile char character;
character=*((char *)(&adad)+2);
return 0;
}
Eu adicionei o volatile
para evitar que a linha de atribuição seja completamente otimizada.Agora, aqui está o que o compilador criou (por -Oz
no meu Mac):
_main:
pushq %rbp
movq %rsp,%rbp
movl $0x00003039,0xf8(%rbp)
movb 0xfa(%rbp),%al
movb %al,0xff(%rbp)
xorl %eax,%eax
leave
ret
As únicas três linhas com as quais nos preocupamos são:
movl $0x00003039,0xf8(%rbp)
movb 0xfa(%rbp),%al
movb %al,0xff(%rbp)
O movl
é a inicialização de adad
.Então, como você pode ver, ele lê o terceiro byte do adad
, e armazena-o de volta na memória (o volatile
está forçando aquela loja a voltar).
Acho que uma boa pergunta é: por que é importante para você qual assembly é gerado?Por exemplo, apenas alterando meu sinalizador de otimização para -O0
, a saída do assembly para a parte interessante do código é:
movl $0x00003039,0xf8(%rbp)
leaq 0xf8(%rbp),%rax
addq $0x02,%rax
movzbl (%rax),%eax
movb %al,0xff(%rbp)
O que é visto de forma bastante direta como as operações lógicas exatas do seu código:
- Inicializar
adad
- Pegue o endereço de
adad
- Adicione 2 a esse endereço
- Carregue um byte desreferenciando o novo endereço
- Armazene um byte em
character
Várias otimizações alterarão a saída...se você realmente precisar de algum modo de comportamento/endereçamento específico por algum motivo, talvez seja necessário escrever o assembly você mesmo.
Outras dicas
Sem saber nada sobre o compilador e a arquitetura subjacente da CPU, nenhuma resposta definitiva pode ser dada.Por exemplo, nem todas as arquiteturas de CPU permitem o endereçamento de cada byte arbitrário na memória (embora eu acredite que todas as arquiteturas atualmente populares o façam):em uma CPU endereçada a palavras, em vez de endereçada a bytes, o que o compilador irá gerar será inevitavelmente o carregamento em algum registro da palavra inteira adad
(presumivelmente por um deslocamento de um registro de ponteiro base, se a variável em questão estiver na pilha [1]), seguido de deslocamento e mascaramento para isolar o byte de interesse.
[1] observe que, sem saber de qual arquitetura de CPU estamos falando e como o compilador a utiliza, não podemos nem dizer se "carregar uma palavra em um deslocamento fixo de um registro base" é algo que é feito inline dentro do instrução (como se poderia esperar, e muitas arquiteturas populares definitivamente suportam ;-) ou precisa de aritmética de endereço separada em um registro auxiliar.
IOW, seja uma boa ideia ou não, é definitivamente possível para definir uma arquitetura de CPU que não pode carregar/armazenar registros exceto de outros registros ou endereços de memória definidos por outros registros ou constantes, e algumas dessas arquiteturas existem (embora possam não ser tão populares no momento ;-).