INE5408 Estruturas de Dados Alocação Dinâmica de Memória As Funções de Alocação Dinâmica de Memória em "C" • Alocação Dinâmica é um meio pelo qual o programa pode obter memória enquanto está em execução. • Já visto até agora: – Constantes são "codificadas" dentro do código objeto de um programa em tempo de compilação. – Variáveis globais (estáticas) têm a sua alocação codificada em tempo de compilação e são alocadas logo que um programa inicia a execução. – Variáveis locais em funções (ou métodos) são alocadas através da requisição de espaço na pilha (stack). #include <stdio.h> char *a, *b; StackPointer Início da Pilha Topo da Memória int func_A () { int local1, local2; … } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Programa void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } #include <stdio.h> char *a, *b; StackPointer Início da Pilha Topo da Memória int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória #include <stdio.h> char *a, *b; StackPointer Início da Pilha Topo da Memória int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB &func_B-#2 local1 local2 HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB &func_B-#2 local1 local2 HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB &func_B-#3 local1 local2 HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB &func_B-#3 local1 local2 HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Topo da Memória #include <stdio.h> char *a, *b; int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } StackPointer Início da Pilha &main-#3 localA localB HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória #include <stdio.h> char *a, *b; StackPointer Início da Pilha Topo da Memória int func_A () { int local1, local2; … } void func_B () { int localA, localB; localA = func_A(); localB = func_A(); } main () { a = "Essa aula é legal"; b = "Será mesmo?" func_B(); } HeapPointer Início da Área Alocável Variáveis estáticas Código objeto Constantes a b 10010101... "Essa aula é ... "Será mesmo.. Sistema Operacional Base da Memória Alocação Dinâmica em "C" • A memória alocada pelas funções de alocação dinâmica é obtida do heap. – O heap é a região de memória livre que se encontra entre o programa (com a área de armazenamento permanente) e a pilha (stack). – O tamanho do heap é, a princípio, desconhecido do programa. • "C" possui duas funções básicas para gerência de memória: – malloc(nº de bytes) - aloca memória. – free(endereço) - libera memória. Função malloc() • Protótipo: void *malloc(size_t número_de_bytes); • Detalhes: – devolve um ponteiro do tipo void (sem tipo) para o início (1º byte) da área de memória alocada; – isto significa que o valor deste ponteiro pode ser atribuído a qualquer variável do tipo ponteiro. • Para isto deve ser utilizado sempre um typecasting. Ex.: se x é um ponteiro para inteiro, então: x = (int *) malloc( sizeof(int) ); – número_de_bytes é a quantidade de bytes alocada; – se a memória for alocada no topo do heap, o heapPointer é atualizado (incrementado de número_de_bytes); – o tipo size_t é definido em stdlib.h. #include <stdlib.h> #include <stdio.h> char *p; int *q; main() { // Aloca 1000 bytes de RAM. p = (char *) malloc(1000); // Aloca espaço para 50 // inteiros. q = (int *) malloc(50*sizeof(int)); } StackPointer Topo da Pilha Topo da Memória HeapPointer Topo da Área Alocável 50*int = 200 bytes 1000 bytes Variáveis estáticas Código objeto p q 10010101... Constantes Sist.Operacional Base da Memória Topo da Memória • malloc devolve: • um ponteiro para a área alocada; • o ponteiro nulo (NULL) caso não seja possível alocar a memória requisitada. • Convém verificar se foi possível alocar a memória. #include <stdio.h> #include <stdlib.h> char *p; main() { ................ // Tenta alocar 1000 bytes de // RAM. p = malloc(1000); // Testa se p é igual a // NULL. if (p == NULL) printf(“%s”, "Sem memória!"); } Espaço de variáveis locais alocado StackPointer Topo da Pilha HeapPointer Topo da Área Alocável Variáveis estáticas Já alocado antes p Código objeto 10010101... Constantes "Sem memória" Sist.Operacional Base da Memória Função free() • Protótipo: void free( void *p ); • Detalhes: – devolve memória previamente alocada ao sistema; – a memória devolvida é aquela que foi alocada com um ponteiro com o valor de p: • o valor de p deve ser um valor que foi alguma vez retornado por malloc(); • não é possível alocar um vetor enorme e depois desalocar a parte dele que "sobrou“; – a utilização de free() com um valor de ponteiro qualquer poder ter resultados catastróficos; – a gerência de buracos no heap é responsabilidade do sistema operacional. Exercício: Lista com um vetor de Ponteiros para Strings • Uma lista ordenada pode conter Strings de qualquer comprimento < 10000; • esta lista tem um número de elementos máximo fixo (100) e é implementada como um vetor de ponteiros para Strings; – utilize as rotinas de lista com vetor que você implementou para a agenda. • Um novo String é lido primeiramente para dentro de uma variável auxiliar qualquer; – então é alocada memória para exatamente o seu tamanho e ele é copiado para esta área. Para copiar um String utilize strcpy(); – por fim um lugar na lista é encontrado para ele. A posição escolhida do vetor de ponteiros da lista é instanciada através da atualização dos valores do ponteiro da posição do String na lista com o endereço do string. Modelagem da estrutura 1 Strings lidos do usuário e alocados no Heap S a b ã o \0 C o n s t i t u i r \0 Lista com Vetor de Ponteiros Modelagem da Lista • Pseudo-código: constantes MAXLISTA = 100; tipo tLista { // Vetor de ponteiros para caracter. caracter *dados[MAXLISTA]; inteiro último; }; • Importante: observe que criando uma variável do tipo tLista você não vai estar alocando memória para os strings a serem lidos, apenas para os ponteiros para eles. Topo da Memória Organização de memória para o exercício StackPointer Topo da Pilha Espaço de variáveis locais alocado HeapPointer Topo da Área Alocável str4 str3 str2 str1 Var.Estáticas Código objeto 10010101... Constantes Sist.Operacional Base da Memória • Para verificar o comprimento de um String: – utilize a função strlen(); – esta função devolve o comprimento (em caracteres imprimíveis) de um string. – Protótipo: int strlen(char *p); #include <stdio.h> #include <stdlib.h> #include <sting.h> char p[90] = "Carro"; main() { printf("%i", strlen(p)); } • Imprime: 5 • Para copiar um String: – utilize a função strcpy(); – esta função copia o conteúdo de um string (dado por um apontador) para a posição de memória dada por outro apontador. – Protótipo: char *strcpy(char *destino, char *fonte); #include <stdio.h> #include <stdlib.h> #include <sting.h> char p[90] = "Carro"; char lata[20]; main() { strcpy(lata, p)); printf("s%", lata); } • Imprime: Carro Detalhes: Lista Ordenada com um vetor de ponteiros para Strings • Como você não sabe o comprimento do String que o usuário vai digitar, use primeiro uma variável auxiliar grande (10000 posições) para guardar o que foi digitado; • a lista deve ser passada como parâmetro para todas as funções que a utilizam, bem como as variáveis de controle da lista; • todas as funções de lista ordenada implementadas anteriormente devem ser reimplementadas para utilizar estes Strings; • para a leitura de um String utilize scanf("%s", entrada). Exercício 2: Trabalho com Passagem de Parâmetros • Agora você vai fazer um programa que manipula mais de uma lista; • o programa fará isto com um único conjunto de funções e passagem das diversas listas como parâmetros; • como aplicação imaginemos um sistema de contabilidade simples; • você vai ter um Plano de Contas constituído por duas listas: débitos e créditos; • o mesmo conjunto de funções (que você já implementou) vai poder ser utilizado para isso: você somente precisa ampliar o conjunto de parâmetros da função para passar por referência também a lista que você quer alterar. – A passagem de parâmetro da lista deve ser por referência porque você deseja que as alterações sejam persistentes. Modelagem de um Lançamento • Cada lista de débitos ou créditos é constituída por lançamentos. Cada lançamento possui: – um valor real (positivo); – um nome. Por exemplo, “Pagar proteção à Mafia” • Estrutura: tipo tLançamento { caracter *nome; real valor; }; Modelagem de um tipo Lista para Débitos ou Créditos • Pseudo-código: constantes MAXLISTA = 100; tipo tListaContábil { tLançamento dados[MAXLISTA]; inteiro último; }; • Importante: observe que criando um vetor de lançamentos, você não vai estar reservando memória para os nomes destes, pois o campo nome é só um ponteiro. Usando (pseudo-código) • Crie variáveis globais: tListaContábil débitos, créditos; • Passe estas variáveis como parâmetros por referência: adiciona(&débitos, nomeLanc, valorLanc); • Cabeçalho: Inteiro FUNÇÃO adiciona(tListaContábil *plano; caracter *nome; real valor); • Importante: nome é passado como ponteiro para caracter. Use um buffer global para ler o nome do lançamento do usuário. Modelagem da estrutura R$ 5,00 1 Strings lidos do usuário e alocados no Heap S a b ã o \0 R$ 505,00 P a s s a g e n s \0 Lista de débitos ou de créditos com Vetor de Estruturas do tipo Lançamento Usando (código “C”) • Referencie diferentemente se estiver usando ponteiros para a lista ou a lista diretamente: tListaContabil debitos, creditos; debitos.dados[2].valor = 5.0; strcpy(debitos.dados[2].nome, buffer); Dentro das funções: Suponha: tListaContabil *ponteiro e ponteiro = &debitos; ponteiro->dados[2].valor = 5.0; strcpy(ponteiro->dados[2].nome, buffer); Headerfile: como garantir Inclusão Única // Arquivo: pilha.h #ifndef EstruturaDaPilha #define EstruturaDaPilha // Definir uma estrutura para a pilha. struct estruturaDaPilha { int topo; int dados[MAXPILHA]; }; // Define um tipo que tem a estrutura da // pilha. typedef struct estruturaDaPilha pilha; #endif Headerfiles: Importante • A diretiva de compilação #ifndef (if not defined) diz que aquela área de código fonte entre o #ifndef e o #endif somente será levada em conta pelo compilador se o argumento de #ifndef ainda não houver sido definido na mesma sessão de compilação no escopo de um módulo; • isso garante que código que a gente "por via das dúvidas" inclui mais de uma vez em um módulo não seja considerado duas vezes; • um exemplo de como isto é útil está na diretiva #include <stdio.h> que está presente tanto em pilha.h quanto em pilha.c e em aplic.c; • como aplic.c carrega pilha.h "para dentro" de si mesmo, carregará também stdio.h. Como está explicitamente também carregando stdio.h, se não houver uma diretiva #ifndef em stdio.h, ele terá o mesmo código existente em stdio.h duas vezes. Módulo: lista.h #ifndef Lista #define Lista typedef struct estruLista { char *elemento[30]; int ultimo; int max; }; typedef struct estruLista lista; #endif Módulo: lista.c #include <stdio.h> #include <stdlib.h> #include “lista.h” lista *criaLista() { lista *nova; nova = malloc( sizeof(lista) ); nova->max = 30; nova->ultimo = -1; return (nova); } void destroiLista(lista *morta) { int i; // Libera memória ocupada pelos Strings. for (i=0; morta->ultimo; i++) free( morta->elemento[i] ); // Libera memória da lista. free( morta ); } Exercício de Implementação 4 Implementação de um programa com número variável de filas com vetores alocadas dinamicamente usando o TAD lista com vetor. Este trabalho é bem fácil porque você já implementou praticamente tudo o que precisa. Só tem agora que modificar um pouco a forma de gerenciar os seus dados. Tome o TAD Fila com Vetor que você já implementou e crie um programa para gerenciar uma lista contendo um número variável e menor do que 20 desses TADs. Cada Fila deverá ter no máximo 20 posições. Para tanto adapte o TAD Lista com Vetor que você implementou de forma que cada elemento da lista seja um tipo ponteiro para um TAD Fila. Quando você for inserir uma nova fila, você a cria dinamicamente, alocando memória para ela e inicializando os seus campos de controle.