08 - Variáveis locais¶
Como visto na expositiva, variáveis locais são armazenadas na pilha. O topo da pilha é armazenado em %rsp
e ela cresce para baixo, ou seja, ao empilhar um dado o valor de %rsp
diminui e ao desempilhar seu valor aumenta. O compilador faz todo o possível para usar somente os registradores, porém em alguns casos é necessário guardar a variável na memória. Isso ocorre, em geral, quando usamos &
para computar o endereço de uma variável. O exemplo mais comum nos códigos que já escrevemos é na leitura de valores usando scanf
.
Exemplo guiado¶
Funções que guardam variáveis na pilha seguem um padrão facilmente identificável. Primeiro elas subtraem um valor da pilha (0x10
no exemplo abaixo) correspondente ao tamanho total de todas as variáveis usadas. Depois temos várias instruções usando endereços relativos a %rsp
e por fim devolvemos o espaço usado somando 0x10
de volta a %rsp
.
sub $0x10, %rsp
. . . // código da função aqui!
movl 0x8(%rsp),%eax
mov %eax,%edx
addl 0xc(%rsp),%edx
. . . // função continua
add $0x10, %rsp
ret
No exemplo acima, temos duas variáveis locais: 0x8(%rsp)
e 0xc(rsp)
. Cada uma é identificada no código Assembly pelo endereço em que está posicionada na pilha. Logo, todo deslocamento em relação a %rsp
indica um acesso a variável local, sendo que pode ser um acesso de leitura e escrita (usando MOV
, por exemplo) ou da operação endereço de &
(usando LEA
).
Conseguimos identificar que seus tamanhos são int
por duas razões:
- elas aparecem em instruções junto com registradores de 4 bytes (
%eax
e%edx
) - as instruções
movl
eaddl
tem o sufixol
, que indica que os dados tratados tem tamanho 4 bytes. Os sufixos suportados são:b
- 1 bytew
- 2 bytesl
- 4 bytesq
- 8 bytes
Qualquer razão acima é suficiente para identificar os tipos das variáveis locais.
Importante
Novamente, nem toda instrução em Assembly pode ser representada em C. As instruções sub 0x10, %rsp
e add 0x10, %rsp
representam a criação de variáveis locais na pilha e não tem equivalente em C. Simplesmente ignoramos elas e usamos as variáveis locais no código.
Revisão de variáveis globais e strings constantes¶
Antes de iniciar o próximo exercício vamos revisar como variáveis locais, globais e strings constantes são acessadas em código assembly. A imagem abaixo exemplifica os três casos:
- Variáveis locais: são acessadas com
lea
(para&
- endereço de) oumov
(para leituras e escritas) relativos a%rsp
- Globais e strings constantes: são acessadas usando a notação
0xYY(%rip)
, sendo que o valor0xYY
muda a cada acesso. No caso das strings, o acesso a estes endereços é somente leitura.
O endereçamento relativo a %rip
leva em conta a posição relativa entre a instrução atual e o endereço de memória do dado. Na imagem acima estão destacadas duas instruções lea
que acessam o mesmo dado. Como o %rip
(ponteiro para a próxima instrução) é diferente precisamos de deslocamentos diferentes para acessar o mesmo dado.
Dica: o gdb coloca o endereço calculado ao lado das instruções deste tipo.
Exercise
É possível que o lea
abaixo seja aritmético? Por que?
lea 0x8(%rsp),%rdx
Answer
Não! O registrador %rsp
é especial e sempre guarda o endereço do topo da pilha de chamadas. Nesse espaço de memória guardamos todas variáveis que não podem ser alocadas em um registrador.
Arquivo ex2
¶
O código abaixo (ex2) utiliza variáveis locais.
Dump of assembler code for function func1:
0x05fe <+0>: sub $0x10,%rsp
0x0602 <+4>: movl $0xa,0xc(%rsp)
0x060a <+12>: movl $0xb,0x8(%rsp)
0x0612 <+20>: lea 0xc(%rsp),%rdi
0x0617 <+25>: callq 0x5fa <func2>
0x061c <+30>: addl $0x1,0x8(%rsp)
0x0621 <+35>: lea 0x8(%rsp),%rdi
0x0626 <+40>: callq 0x5fa <func2>
0x062b <+45>: add $0x10,%rsp
0x062f <+49>: retq
Vamos começar analisando as três primeiras linhas do programa.
Exercise
Quanto espaço (em bytes) é reservado na pilha?
- 10
- 16
- 20
- 8
Answer
Ao executar sub $0x10,%rsp
criamos um espaço de 16 bytes (0x10
) na pilha. Note que as constantes sempre aparecem em hexadecimal.
Existem vários momentos no código que lemos/escrevemos em endereços relativos a %rsp
. Cada deslocamento representa uma variável local que está armazenada na memória.
Exercise
Quantas variáveis são inicializadas e quais seus tamanhos?
- 2 -
int
- 2 -
long
- 1 -
int
- 1 -
long
Answer
São duas variáveis int
: uma em 0xc(%rsp)
e outra em 0x8(%rsp)
.
Exercise
Identifique onde as variáveis locais encontradas são usadas. Dê um nome para cada uma delas.
Answer
Vou utilizar os identificadores e tipos int a
e int b
.
Exercise
Os lea
das linhas +20
e +35
podem ser aritméticos?
- SIM
- NÃO
Answer
Não são aritméticos, portanto representam a operação endereço de &
.
Exercise
Com base em sua resposta acima, traduza as chamadas de função que ocorrem nas linhas +25
e +40
.
Answer
Na linha +25
ocorre a chamada func2(&a)
. Já na linha +40
a chamada é func2(&b)
Exercise
Traduza o programa acima para C
Answer
void func2(int *a) {
*a += 1;
}
void func1() {
int a = 10;
int b = 11;
func2(&a);
b++;
func2(&b);
}
Arquivo ex3
¶
No exercício anterior vimos como passar variáveis por referência para outras funções. Agora veremos como trabalhar com scanf
. Veja abaixo a função main
do executável ex3
. Abra este arquivo usando o gdb e siga os exercícios.
Dump of assembler code for function main:
0x1149 <+0>: sub $0x18,%rsp
0x114d <+4>: lea 0xc(%rsp),%rsi
0x1152 <+9>: lea 0xeab(%rip),%rdi # 0x2004
0x1159 <+16>: mov $0x0,%eax
0x115e <+21>: callq 0x1040 <__isoc99_scanf@plt>
0x1163 <+26>: cmpl $0x0,0xc(%rsp)
0x1168 <+31>: js 0x1180 <main+55>
0x116a <+33>: lea 0xe9f(%rip),%rdi # 0x2010
0x1171 <+40>: callq 0x1030 <puts@plt>
0x1176 <+45>: mov $0x0,%eax
0x117b <+50>: add $0x18,%rsp
0x117f <+54>: retq
0x1180 <+55>: lea 0xe80(%rip),%rdi # 0x2007
0x1187 <+62>: callq 0x1030 <puts@plt>
0x118c <+67>: jmp 0x1176 <main+45>
Exercise
Vamos começar procurando por variáveis locais que estejam na pilha. Quanto espaço é reservado para elas? Liste abaixo as que você encontrou e dê um nome para cada uma.
Dica: todo acesso relativo a %rsp
representa um acesso a variável local.
Answer
Estão sendo reservados 24 bytes na pilha. Temos uma variável local int n
.
Exercise
A instrução call
em main+21
é um scanf
. O primeiro argumento é a string de formatação. Use o comando x
do gdb para encontrar ela na memória.
Dica: usamos este comando nas aulas 02 e 03.
Answer
A string de formatação é "%d".
Exercise
O segundo argumento do scanf
é o endereço da variável a ser preenchida. O endereço de qual variável local é passado?
Answer
É o endereço da variável correspondente a 0xc(%rsp)
Exercise
Reconstrua a chamada do scanf
acima.
Answer
int a; // demos esse nome para todas ocorrências de 0x(%rsp)
scanf("%d", &a);
Com a chamada do scanf
pronta, vamos analisar o restante do código.
Exercise
Agora examinaremos as chamadas em main+40
e main+62
. Elas são para a função puts
. Veja sua documentação (procure por C puts.) e explique abaixo o quê ela faz e quais são seus argumentos.
Answer
Escreve os múltiplos caracteres de uma string, seguido de \n
, na saída padrão
Exercise
Com base na explicação acima, escreva abaixo os argumentos passados para cada chamada.
Answer
São feitas duas chamadas: a primeira é puts("Positivo")
e a segunda é puts("Negativo")
.
Exercise
Traduza o código acima para um versão em C.
Answer
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
if (n < 0) {
printf("Negativo\n");
} else {
printf("Positivo!\n");
}
return 0;
}
Entrega¶
Exercícios 4 e 5 da atv6 disponíveis no seu repositório de atividades privado.
Atenção!
Antes de continuar, dê um git pull
em seu repositório de atividades!
Importante
Sempre leia o README.md
disponível na pasta da atividade. Ele tem informações importantes para a execução da atividade.