Programação imperativa
10. Pilha de execução
A pilha de execução.
Memória dinâmica.
10. Pilha de execução
A pilha de execução.
Memória dinâmica.
Pilha de execução
Em inglês, runtime stack.
A pilha de execução é formada por uma sequência de registos de ativação, cada um deles correspondendo a uma ativação de uma função, gerida em modo pilha. Em inglês, registo de ativação é stack frame.
Na presença de recursividade, pode haver na pilha várias
frames relativas a uma mesma função.
Na frame há espaço de memória para os argumentos da função, para as variáveis locais e para o endereço de
retorno.
As frames relativas a uma função têm todas o mesmo
Observando a pilha de execução
Este é o programa que
vamos usar, para observar a
pilha de execução.
#include <stdio.h> int g(int *a, int n) { int result = 0; int i; for (i = 0; i < n; i++) result += a[i]; return result; }
int f(int x, int y) { int result; int a[10]; int i = 0; for (i = 0; i < y; i++) a[i] = x;
result = g(a, y); return result; void t5(void) { int x; int y; x = g(primes, 10); y = f(3, 6); printf("%d %d\n", x, y); } int main(void) { t5(); return 0; }
Janela da pilha de execução
A função main chamou a função t2, que chamou a função g.
...
x = g(primes, 10); ...
int g(int *a, int n) { int result = 0; int i; for (i = 0; i < n; i++) result += a[i]; return result; }
Nesta altura, a variável result já foi
inicializada mas a variável i ainda não. O argumento n vale 10 e o argumento a referencia o array primes.
Janela da pilha de execução (2)
A função main chamou a função t2, que chamou a função f, que chamou a função g.
...
y = f(3, 6); ...
int f(int x, int y) { int result; int a[10]; int i = 0; for (i = 0; i < y; i++) a[i] = x;
result = g(a, y); return result; }
Note bem: o argumento a referencia a variável a da função f. Terem o mesmo nome é apenas uma coincidência.
Observando a recursividade
Se uma função se
chama a si própria, na
pilha de execução
passaremos a ter dois
frames seguidos, cada
um para cada ativação
da função.
Usemos o seguinte
exemplo, com a
função fatorial:
#include <stdio.h> int fact(int x) { return x == 0 ? 1 : x * fact(x-1); } void t6(void) { int z; z = fact(8); printf("%d\n", z); } int main(void) { t6(); return 0; }A função fact chama-se a si própria, quando o
argumento é diferente de zero.
Observando a recursividade (2)
Aqui estamos a desempilhar: a função retornou 120, quando x valia 5 e agora está na frame onde x vale 6.
Aqui estamos a empilhar: a função main chamou t3, que chamou fact, que chamou fact, que chamou fact, que chamou fact, que chamou fact, que chamou fact. Nesta altura, o argumento é 3.
Memória da pilha de execução
Usemos a seguinte função para inspeccionar a
memória da pilha de execução.
int facta(int x) { int v[2]; v[0] = primes[x]; v[1] = powers[x]; return x == 0 ? 1 : x * facta(x-1); } void t7(void) { int z; z = facta(8); printf("%d\n", z); }
O array v serve só para nos ajudar a localizar a
memória de cada ativação da função facta.
Memória da pilha de execução (2)
A memória da pilha de execução, logo após
chamada facta(6), antes da inicialização do array v. Conseguimos localizar as posições de memória onde estão os argumentos
(marcadas a azul) e as sucessivas ativações do array v (marcadas a vermelho).
10. Pilha de execução
A pilha de execução.
Memória dinâmica.
Memória dinâmica
A função f usada no exemplo da pilha de execução é insegura: se y for maior do que 10, o array a, declarado na função, vai transbordar.
Qual deve então ser a capacidade do array a?
Deve ser y!
Só que o valor de y não pode ser determinado no momento da compilação.
A capacidade dos arrays estáticos ou automáticos deve ser uma constante, conhecida no momento da
compilação.
Isso permite que a frame de cada função tenha um
tamanho determinado durante a compilação.
malloc
A função malloc requisita ao sistema de operação o número de bytes indicado no argumento.
Observe a função f1, equivalente a f, mas onde o array a é alocado dinamicamente, com malloc:
int f1(int x, int y) {
int result;
int *a = (int *) malloc(y * sizeof(int));
int i = 0;
for (i = 0; i < y; i++) a[i] = x;
result = g(a, y);
free(a);
return result; }
Dizemos que o array a é alocado dinamica-mente ou que fica na memória dinâmica.
No fim da função, devemos libertar a memória alocada dinamicamente.
Para usar malloc, temos de fazer #include <stdlib.h>
O número de bytes é o número de elementos vezes o número de bytes de cada elemento.
Experimentando f1
void t8(void) { int x; x = f(1, 10); printf("%d\n", x); x = f(1, 10000); printf("%d\n", x); } void t81(void) { int x; x = f1(1, 10); printf("%d\n", x); x = f1(1, 10000); printf("%d\n", x); } 10 10 10000Press any key to continue . . .
Onde está a memória dinâmica?
Procuremos, com a seguinte função:
int f2(int x, int y) {
int result;
int *a = (int *) malloc(y * sizeof (int)); int b[10]; int i = 0; for (i = 0; i < y; i++) a[i] = x; for (i = 0; i < 10; i++) b[i] = x * i;
result = g(a, y); free(a);
return result; }
Com este exemplo,
teremos os arrays primes e powers na memória estática, o array b na
memória automática e o array a na memória
Observando a memória dinâmica
Estática
Automática
Situação mesmo antes da instrução free na função f2.
10. Pilha de execução
A pilha de execução.
Memória dinâmica.
Conversão hexadecimal-decimal
Note bem: não há números hexadecimais. Há sim a notação hexadecimal.
Problema: representar decimalmente um número dado usando a representação
hexadecimal. Observe:
void hex2dec(void) {
int x;
while (scanf("%x", &x) != EOF) printf("%d\n", x); } 0022fb60 2292576 cccccccc -858993460 00000001 1 00000010 16 00000100 256 00001000 4096 00010000 65536 00100000 1048576 01000000 16777216 10000000 268435456 7fffffff 2147483647 80000000 -2147483648 ffffffff
Conversão decimal-hexadecimal
Para converter no sentido decimal ->
hexadecimal é parecido:
void dec2hex(void) {
int x;
while (scanf("%d", &x) != EOF) printf("%x\n", x); } 10 a 100 64 64 40 204 cc 2147483647 7fffffff -1 ffffffff 255 ff 160 a0 170 aa 17 11 26 1a