Programação Imperativa Lição n.º 16 A pilha de execução
A pilha de execução Registos de execução. Arrays locais. Caso da alocação dinâmica. Segmento de dados. 11/17/16 Programação Imperativa 2
A pilha de execução Quando uma função é chamada, o sistema de execução aloca automaticamente espaço para as variáveis locais da função (incluindo argumentos) na pilha de execução (em inglês runtime stack). A pilha de execução chama-se pilha, porque é gerida em pilha. Isto é, quando uma função g é chamada por uma função f, as variáveis de g são guardadas na memória a seguir às variáveis de f. A seguir significa em endereços mais altos. Portanto, as variáveis de g ficam por cima das de f. Quando a função g retorna, é como se as suas variáveis saíssem de cima. 11/17/16 Programação Imperativa 3
Registos de ativação 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 para os argumentos da função, para as variáveis locais e para informação de controlo. As frames relativas a uma função têm todas o mesmo tamanho, que é determinado pelo compilador. 11/17/16 Programação Imperativa 4
Observando a pilha Usaremos este programa. A função main chama a função de teste t1, que chama a função h1, que chama a função f1. As constantes usadas têm valores cuja representação hexadecimal é mais fácil de localizar na memória. Observaremos a memória à entrada de f1 à saída de f1, à saída de h1 e à saída de t1. int f1 (int x, int y) int a = 204; // 0x000000CC int b = x + 1; int c = y + 5; int d = a + b + c; return d; int h1(void) int p = 68; // 0x00000044 int q = 34; // 0x00000022 int r = f1(p, q); int s = r + 1; return s; void t1(void) int x = 85; // 0x00000055 int y = h1(); int z = x + y; printf("valor de z = %d\n", z); int main(void) t1(); return 0; 11/17/16 Programação Imperativa 5
Primeira observação: à entrada de f1 11/17/16 Programação Imperativa 6
Frame pointer Note, a cinzento, o frame pointer, isto é, o endereço do início da frame de cada função (o qual corresponde à variável marcada a cor de laranja). Por exemplo 7FFF5FBFF810 é o endereço da variável p, a cor de laranja, com valor 0x00000044 na quarta linha; aqui começa a frame da função h1. E note que, por acaso, as variáveis a (a azul) e b (a verde) na primeira linha contêm lixo que parece corresponder a algum anterior frame pointer. 11/17/16 Programação Imperativa 7
Segunda observação: à saída de f1 Nota: 0x00000138 é 256 + 3 * 16 + 8 = 312; 0x00000027 é 2 * 16 + 7 = 39; 0x00000045 é 16 * 4 + 5 = 69; 0x000000CC é 12 * 16 + 12 = 204; 11/17/16 Programação Imperativa 8
Terceira observação: à saída de h1 Nota: 0x00000138 é 256 + 3 * 16 + 8 = 312; 0x00000139 é 256 + 3 * 16 + 9 = 313. 11/17/16 Programação Imperativa 9
Quarta observação: à saída de t1 Nota: 0x0000018E é 256 + 8 * 16 + 14 = 398; 11/17/16 Programação Imperativa 10
Segunda experiência Neste caso, temos uma função que chama duas outras funções. Vamos observar a memória à saída de f2, depois em h2 entre as duas chamadas, depois à saída de g2 e por fim à saída de h2. int f2 (int x, int y) int a = 204; // 0x000000CC int b = x + 1; int c = y + 5; int d = a + b + c; return d; int g2 (int z) int i = 221; // 0x000000DD int j = z + i; return j; int h2(void) int p = 68; // 0x00000044 int q = 34; // 0x00000022 int r = f2(p, q); int s = r + 1; int t = g2(p+1); int u = t + s; return u; void t2(void)... 11/17/16 Programação Imperativa 11
Observações 11/17/16 Programação Imperativa 12
Funcionamento em pilha Este exemplo ilustra o funcionamento em pilha da pilha de execução. As variáveis de g2 foram para a memória acima das variáveis de h2, precisamente na zona onde antes tinham estado as variáveis de f2, anteriormente chamada por h2. Quando a pilha cresce, apanha a memória tal como ela estava: se nos tivéssemos esquecido de inicializar alguma variável local, o lixo que memória continha serviria de valor inicial... Curiosamente, a pilha cresce ao contrário, isto é, cresce para endereços menores. 11/17/16 Programação Imperativa 13
Recursividade É este funcionamento em pilha que permite a existência de funções recursivas. Quando uma função se chama a si própria, diretamente ou indiretamente, haverá várias frames dessa função na pilha. int f4(int x) int result = 119; if (x == 1) result = 0; else result = 1 + f4(x/2); return result; void t4(void) int x = 85; // 0x00000055 int y = f4(x); printf("valor de y = %d\n", y); printf("valor de x = %d\n", x); 11/17/16 Programação Imperativa 14
Observando a recursividade 11/17/16 Programação Imperativa 15
Arrays na memória Agora, um exemplo com arrays. Neste caso, vamos observar a memória antes da declaração do array, à saída da função f3 e à saída de t3. int f3(int x) int b = 204; // 0x000000CC int m = 8; int a[8]; a[0] = x; a[1] = x+1; a[2] = x+2; a[3] = x+3; a[4] = x+4; a[5] = x+5; a[6] = x+6; a[m-1] = x+7; int c = a[0] + a[7]; int d = a[1] + a[2]; int e = b + c + d; return e; void t3(void) int x = 85; // 0x00000055 int y = f3(x); printf("valor de y = %d\n", y); printf("valor de x = %d\n", x); 11/17/16 Programação Imperativa 16
Observações com arrays Que acontecerá se na função f3 acrescentarmos a[m+3] = 119? E a[m+7] = 119? E a[m+11] = 119. (119 é 0x00000077). 11/17/16 Programação Imperativa 17
Memória dinâmica A memória dinâmica está no heap. Mas onde é que está o heap? Procuremos, substituindo o array automático na função anterior por um array dinâmico. Observemos a memória, antes do malloc, depois do malloc e à saída da função f3. int f3(int x) int b = 204; // 0x000000CC int m = 8; // int a[8]; int *a = malloc(m * sizeof(int)); a[0] = x; a[1] = x+1; a[2] = x+2; a[3] = x+3; a[4] = x+4; a[5] = x+5; a[6] = x+6; a[m-1] = x+7; int c = a[0] + a[7]; int d = a[1] + a[2]; int e = b + c + d; return e; void t3(void) int x = 85; // 0x00000055 int y = f3(x); printf("valor de y = %d\n", y); printf("valor de x = %d\n", x); 11/17/16 Programação Imperativa 18
Observações com arrays dinâmicos Tecnicamente, a é um apontador, e ocupa 8 bytes: Aqui, o apontador já está inicializado: Um apontador é uma variável cujo valor é um endereço. A memória alocada para o array a ainda não está inicializada. Repare que os endereços no heap são de uma gama completamente diferente dos endereços na pilha. 11/17/16 Programação Imperativa 19
Inicialização dos arrays dinâmicos 11/17/16 Programação Imperativa 20
Memória estática A memória estática está no segmento de dados: #include <stdio.h> #include <stdlib.h> const int day_in_month[12] = 31,28,31,30, 31,30,31,31, 30,31,30,31 ; const int primes[100] = 2,3,5,7,11,13,17,19,23,29,... ; int f3(int x)... A memória estática é inicializada antes mesmo de... a função main arrancar. Note que a gama de endereços é diferente da pilha e do heap. 11/17/16 Programação Imperativa 21
Cadeias literais na memória estática As cadeias de carateres que usamos nos printf, por exemplo, estão na memória estática também... void t3(void) int x = 85; // 0x00000055 int y = f3(x); printf("valor de y = %d\n", y); printf("valor de x = %d\n", x);... Observamos que as cadeias vêm a seguir ao array dos primos. 11/17/16 Programação Imperativa 22