9 - GERENCIAMENTO DE MEMÓRIA 9.1 - LISTAS GENERALIZADAS Conceito Operações sobre listas Exemplos Representação Encadeada de Listas Método dos ponteiros Método de cópia Exemplo “List Headers” Listas Generalizadas em C Exemplo de implementação da operação tail Exemplo de implementação da operação head Exemplo de implementação da operação addon Exemplo de implementação da operação settail 2 2 2 3 3 4 4 5 5 6 6 6 7 7 8 9.2 - GERENCIAMENTO AUTOMÁTICO DE LISTAS Método do Contador de Referências Coleta de Resíduos Algoritmos para Marcação Primeira versão Segunda versão Terceira versão Tipo de registros para a marcação de blocos de memória Estrutura de um nó no processo de marcação Algoritmos das funções Algoritmos para Compactação Estrutura de um nó no processo de compactação Algoritmos da função 9 9 9 11 11 11 11 12 12 12 17 17 18 9.3 - GERENCIAMENTO DINÂMICO DE MEMÓRIA Conceito Primeiro Ajuste Melhor Ajuste Pior Ajuste Exemplos 21 21 21 21 21 21 Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 1 9 - GERENCIAMENTO DE MEMÓRIA 9.1 - LISTAS GENERALIZADAS Conceito Uma lista generalizada é um tipo abstrato de dados. A lista é uma seqüência de objetos denominados elementos. Associado a cada elemento da lista existe um valor. Listas encadeadas referem-se à implementação de listas por encadeamento. Os elementos de uma lista não necessitam ser todos do mesmo tipo de dados. A implementação mais comum de listas generalizadas é feita por encadeamento. Existem dois tipos de ponteiros: externos e internos. Os primeiros são ponteiros não contidos nos nós da lista enquanto os ponteiros internos o são. Normalmente referenciam-se uma lista por meio de um ponteiro para a lista(ponteiro externo). Elementos de listas que sejam também listas são representados por elementos cujos valores contenham ponteiros para estas últimas listas. Listas podem ser denotadas por uma enumeração de seus elementos, separados por vírgulas e delimitada (a enumeração) por parênteses. Listas vazias podem ser denotadas por um símbolo especial ( , por exemplo) ou por “( )”. EXEMPLOS: Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 2 Operações sobre listas Operação head (L) tail (L) first (L) info (x) next (x) nodetype (x) push (L, v) addon (L, v) setinfo (x, v) sethead (L, v) setnext (x, y) settail (L1, L2) Descrição retorna o valor do primeiro elemento de L retorna a lista obtida removendo-se o primeiro elemento de L. Uma sublista de L é uma lista obtida pela aplicação de zero ou mais operações tail a L. retorna o primeiro elemento de L. L e first (L) indicam o mesmo nó retorna o valor do elemento x. head (list) = info (first (list)) retorna o elemento sucessor de x na lista. retorna o tipo de dados do valor do elemento x. adiciona um elemento com valor v na frente da lista L, modificando o valor de L (é um procedimento e não uma função) função que retorna uma nova lista que tem v como head e L como tail. /* push (L, v) eqüivale a L = addon (L, v) */ atualiza o valor do elemento x de uma lista para v. set info (x, v) é implementada por info (x) = v atualiza o valor do primeiro elemento de L para v. sethead (L, v) = setinfo (first (L), v) = = setinfo (L, v) ou a equivalente a L = addon (tail (L), v) altera a lista que contém o elemento x da forma next (x) = y concatena a lista L2 ao primeiro elemento da lista L1. Operação inversa à operação tail. settail (L1, L2) = setnext (first (L1), first (L2)) = setnext (L1, L2) settail (L1, L2) eqüivale a L1 = addon (L2, head (L1)) Um nó n é acessível de uma lista L se existe uma seqüência de operações head e tail que, aplicada a L, fornece uma lista na qual n seja seu primeiro elemento. Exemplos L1 = (8, ‘x’, 300, 5, ‘y’) L2 = (3, (7, 14, (13, 26, 39), ( ), 21), 30, (4, 40, 400)) L3 = (100, 200, 300) L4 = (215, 230, 245) head (L1) = 8 tail (L1) = (‘x’, 300, 5, ‘y’) head (tail (L1)) = ‘x’ tail (tail (L1)) = (300, 5, ‘y’) tail (L2) = (( 7, 14, (13, 26, 39, ( ), 21), 30, (4, 40, 400)) head (tail ( (L2)) = (7, 14, (13, 26, 39, ( ), 21) head (head (tail (L2))) = 7 Operação L5 = NULL push (L5, 12) push (L5, 14) push (L5, 16) L6 = addon (L5, 18) sethead (L3, 50) sethead (L3, L4) settail (L1, L3) Resultado L5 = ( ) L5 = (12) L5 = (14,12) L5 = (16,14,12) L6 = 18,16,14,12 L3 = (50,200,300) L3 = ((215,230,245),200,300) L1 = (8, ((215,230,245),200,300)) Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 3 settail (L2, (3,6,9)) L2 = (3,3,6,9) Representação Encadeada de Listas O conceito abstrato de listas é geralmente implementado por uma lista encadeada de nós. Cada elemento da lista abstrata corresponde a um nó da lista encadeada. Cada nó da lista possui campos info e next. Um ponteiro para um nó de uma lista também representa a sub lista representada pelos nós entre o nó apontado e o fim da lista original. Operações básicas sobre listas tais como addon e tail podem ser implementados por dois métodos: método dos ponteiros e método de cópia. No método dos ponteiros sempre que uma lista Lx receber como sub lista uma lista Ly o que ocorre é que o elemento qntecessor da sub lista tem seu ponteiro next apontado para Ly. Mo método de cópia o que se faz é inserir após o antecessor da sub lista na lista Lx uma cópia da cada nó de Ly. Estes nós são duplicados ficando os originais em Ly e uma cópia na sub lista de Lx. EXEMPLO: L7 = (8, 13, 5) L8 = addon (L7,19) Método dos ponteiros Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 4 Método de cópia Por razões de eficiência a maioria dos sistemas de processamento de listas usa o método dos ponteiros. Neste método o número de operações envolvidas na inclusão ou exclusão de um elemento a uma lista independe do tamanho da lista. Todavia essa eficiência do método obriga o usuário a tomar cuidado com o efeito que a mudança em uma lista possa acarretar em outras listas. Sistemas de processamento de listas que usem o método dos ponteiros dispõem também de operações explícitas de cópia de listas. Uma opção que atinge o projeto refere-se ao dilema: Listas e elementos que aparecem mais de uma vez em uma lista generalizada devem ser replicados ou devem ser apontados por ponteiros confluentes? A operação crlist serve para congregar diversas operações addon. Exemplo crlist (a1,a2,..., an) eqüivale a L9 = NULL L9 = addon (L9, a1) L9 = addon (L9, a2) L9 = addon (L9,an) Quando as operações de listas são implementadas pelo método dos ponteiros é possível criar listas recursivas. L10 = crlist (30, 40, 50, 60) L11 = crlist (100, L10, 200, L10, 60) ou L11 = (100, crlist (30, 40, 50, 60), 200, crlist (30, 40, 50, 60), 60) Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 5 “List Headers” “ List headers” ou cabeças de listas são nós que antecedem listas. Nestes nós especiais o atributo de informação armazena informações globais sobre a lista (número de nós ou ponteiro para o final da lista, por exemplo). “Headers” podem fornecer uma terceira alternativa, abaixo descrita, de implementação de listas generalizadas, além dos métodos dos ponteiros e método de cópia. Os “list headers” sempre são posicionados no início de qualquer grupo de nós que deve ser considerado uma lista. Ponteiros externos sempre apontam para nós tipo “header”. Quaisquer parâmetros que representem listas devem ser implementados como ponteiros para o “header” das respectivas listas. Funções que retornem listas devem ser implementados retornando ponteiros para nós tipo “header”. Geralmente os átomos são tratados pelo método de cópia e as sub listas são tratadas pelo método dos ponteiros. Um problema complexo é descobrir quais os nós que podem ser liberados depois de operações que os tornem sem utilização. Listas Generalizadas em C Os nós de listas generalizadas podem conter dados de diversos tipos, além de ponteiros para outras listas. Uma implementação que permite manipular esta diversidade é feita pela declaração de nós de listas como sendo tipo Union contendo registros e ponteiros pra sub listas e, caso necessario, variantes tipo caractere, inteiro, real, etc., como por exemplo: define REG define LST 1 2 struct nodetype { int utype ; /* utype pode valer REG ou LST */ union { struct reg area_de_dados; /* o tipo de registro reg depende da natureza do problema */ struct nodetype *lstinfo; } info; struct nodetype *next; }; /* a estrutura ou registro nodetype possui três atributos: tipo ou área de dados ou ponteiro para sub lista ponteiro para sucessordo tipo nodetype */ typedef struct nodetype *NODEPTR; Exemplo de implementação da operação tail NODEPTR tail (NODEPTR list) { if (list == NULL){ prinft ( “Operação ilegal de tail em lista vazia \n”); exit (1); } else return (list->next); /* do registro apontado por list seleciona-se o atributo next } /* end tail */ */ Considere-se outra definição de tipo de dado ou estrutura. Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 6 struct { int utype; union { struct reg area_de_dados; /* o tipo de registro reg depende da natureza do problema */ struct nodetype *lstinfo; } info; } infotype; /* a estrutura ou registro infotype possui dois atributos: tipo ou área de dados ou ponteiro para sub lista */ typedef struct infotype * INFOPTR; struct nodetype { INFOPTR item; struct nodetype * next; }; /* a estrutura ou registro nodetype possui tres atributos: ponteiro para registro do tipo infotype ponteiro para sucessor do tipo nodetype */ typedef struct nodetype *NODEPTR; Exemplo de implementação da operação head void head ( NODEPTR list, INFOPTR p_item) { if ( list == NULL) { printf ( “Operação ilegal. Header de lista vazia \n”); exit(1); } p_item = (INFOPTR) malloc (sizeof (struct infotype)); p_item->utype = list->item->utype; /* do registro tipo infotype apontado por p_item seleciona-se o atributo utype; do registro tipo nodetype apontado por list seleciona-se o atributo item; do registro tipo infotype apontado por list->item seleciona-se o atributo utype */ p_item->info = list->item->info; /* do registro tipo infotype apontado por p_item seleciona-se o atributoinfo; do registro tipo nodetype apontado por list seleciona-se o atributo item; do registro tipo infotype apontado por list->item seleciona-se o atributo info */ return; } /* end head */ Exemplo de implementação da operação addon NODEPTR addon ( NODEPTR list, INFOPTR p_item) } NODEPTR novo; novo = ( INFOPTR) malloc ( sizeof (struct nodetype)); novo->item->utype = p item->utype; /* do registro tipo infotype apontado por p_item seleciona-se o atributo utype; do registro tipo nodetype apontado por novo seleciona-se Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 7 novo->item->info = p item->info; novo->next = list; return (novo); } o atributo item; do registro tipo infotype apontado por novo->item seleciona-se o atributo utype */ /* do registro tipo infotype apontado por p_item seleciona-se o atributo info; do registro tipo nodetype apontado por novo seleciona-se o atributo item; do registro tipo infotype apontado por novo->item seleciona-se o atributo info */ /* do registro tipo nodetype apontado por novo seleciona-se o atributo next */ /* end addon */ Exemplo de implementação da operação settail void settail ( NODEPTR plist, NODEPTR p_tail) { NODEPTR p; p = plist->next; plist é um ponteiro para lista; do registro apontado por plist seleciona-se o atributo next */ plist->next = p_tail; freelist (p); } /* end settail */ Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 8 9.2 - GERENCIAMENTO AUTOMÁTICO DE LISTAS O gerenciamento automático de listas normalmente é feito pelos métodos do contador de referências e da coleta de resíduos. Método do Contador de Referências Neste método cada nó possui um campo contador que armazena o número de ponteiros (internos e externos) que apontam para este nó. Toda vez que o ponteiro para um nó é atualizado os contadores de referências dos nós para os quais o ponteiro apontava e passou a apontar são atualizados. Pelo método do contador de referências a operação L = tail(L) é implementada como p = L; L = next(L); next(p) = NULL; reduce(p); Toda vez que o contador de referências de um nó se torna nulo este nó pode ser devolvido à lista de nós livres. A atualização do contador de referências, no caso de redução pode ser feito como: reduce (NODEPTR p) { if ( p != NULL) { p->count --; if ( p->count == 0) { r = pnext; reduce (r); /* Se p tem sucessor reduzir o contador de referências de p implica em reduzir o contador de referências de todos os seus sucessores */ if ( nodetype (p) == LST) /* Se p é um cabeça de sub lista reduzir o contador de referências de p implica em reduzir o contador de referências de todos os elementos da sub lista */ reduce (head (p)); freenode (s); } } Este método exige muito esforço do sistema a cada comando de manipulação de listas. Sempre que o valor de um ponteiro é atualizado todos os nós préviamente acessíveis através daquele ponteiro podem potencialmente ser liberados. Frequentemente o esforço computacional de identificação dos nós a liberar não compensa o espaço recuperado. Muitas vezes vale mais a pena esperar o final do programa e recuperar toda a memória utilizada. Uma alternativa a este método consiste na utilização de contadores de referência apenas nos nós cabeças de lista (“headers”). Mesmo assim é difícil tratar listas circulares ou recursivas, por exemplo. Coleta de Resíduos Coleta de resíduos é um processo de coleta de todos os nós fora de uso e seu retorno à lista de espaço disponível. Este processo é executado em duas fases. - Fase de marcação A fase de marcação serve para marcar todos os nós acessíveis por meio de ponteiros externos, que são os nós que estão em uso. A marcação é feita pela incorporação de um atributo marca que recebe o valor Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 9 TRUE quando o nó é acessível por ponteiros externos. No início e no final do processo de coleta de resíduos todos os atributos marca devem conter o valor FALSE. - Fase de compactação Na fase de compactação a memória é percorrida sistematicamente liberando todos os nós não marcados e realocando os nós ainda em uso. A marcação dos nós exige o uso de atributos de marcação nos nós ou em tabelas específicas. A coleta de resíduos é invocada quando existe pouco espaço disponível e, em geral, exige a interrupção de outros processos. Sistemas de tempo real devem utilizar algoritmos mais elaborados que o de coleta de resíduos. É possível que a coleta de resíduos ocorra quando quase todos os nós estejam em uso. Nessa situação recupera-se muito pouco espaço. Em conseqüência logo a seguir pode haver necessidade de invocar novamente o processo de coleta de resíduos que, mais uma vez, de pouco adiantará, caindo-se em um círculo vicioso denominado “thrashing”. Para evitar este problema, sempre que um coletor de resíduos não conseguir recuperar uma porcentagem especificada do espaço de memória, o processo que requereu memória adicional é descontinuado. Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 10 Algoritmos para Marcação Os algoritmos para marcação de nós em uso serão apresentados em 3 versões em ordem crescente de complexidade cujas etapas principais são as seguintes: Primeira versão Marcar inicialmente todos os nós imediatamente acessíveis. Efetuar diversos passos sequenciais sobre a memória. Em cada passo quando um nó marcado for encontrado deve-se marcar todos os nós apontados por ponteiros saídos desse nó. Segunda versão Suponha-se queum nó n1 foi marcado e contém ponteiro para um nó não marcado n2. Marcar n2 Prosseguir com o nó sucessor de n1. Se o endereço de n2 for menor que o endereço de n1, prosseguir com o nó sucessor de n2 em vez do sucessor de n1. Terceira versão Nesta versão, em vez de percorrer sequencialmente a memória, percorre-se as listas acessíveis. Usa-se uma pilha auxiliar. Ao percorrer uma lista acompanhando os ponteiros next examina-se os atributos tipo. Quando o tipo for LIST então o valor do atributo lstinfo(ponteiro para uma sub lista) daquele nó é empilhado. Ao terminar uma lista ou ao encontrar um nó já marcado efetua-se um desempilhamento e atravessa-se a lista apontada pelo topo da pilha. Uma solução alternativa para evitar o eventual transbordamento da pilha consiste em usar uma pilha de tamanho máximo. Caso a pilha esteja na iminência de transbordar pode-se reverter ao método sequencial anterior. Pode-se também contornar a solução usando as próprias listas de nós como pilha. Para fazer isto temporariamente desarrumam-se os ponteiros das listas e depois da marcação dos nós rearrumam-se as listas. O problema fundamental do percurso de uma rede de listas encadeadas em uma só direção consiste em, ao chegar ao final de uma lista, determinar qual o ponto de onde se deve reiniciar o percurso. Uma das maneiras de resolver este problema consiste no empilhamento dos pontos aonde se iniciam as listas fora do percurso original. O modelo empregado para os nós tem 4 atributos: Marca (TRUE ou FALSE) Tipo (REG ou LST) Next (ponteiro para sucessor na lista) Info reginfo lstinfo Considera-se p o nó a ser processado e top um ponteiro para o topo da pilha de nós percorridos, isto é, top aponta o antecessor de p. O ponterio next de top, ao invés de apontar p, como vinha fazendo, temporariamente aponta o antecessor de top na lista. Assim pode-se retornar na lista. Quando se está iniciando uma sub-lista o atributo lstinfo contém um ponteiro para o primeiro elemento da sub lista e passa a apontar o antecessor do nó (pai da sub-lista) na lista original. Nessa situação troca-se o seu tipo para STK (3). Ao se desempilhar um nó se o tipo não for STK deve-se restaurar o atributo next. Se o tipo for STK deve-se restaurar o atributo lstinfo e o tipo deve voltar a ser LST. Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 11 Tipo de registros para a marcação de blocos de memória reg atributo chave outros tipo chave demais atributos da entidade Estrutura de um nó no processo de marcação Atributo marca É union ? Não Componentes - Tipo inteiro tipo Não - inteiro info Sim next Não reginfo lstinfo - reg inteiro inteiro Descrição Indicador se o nó foi marcado como estando em uso. Pode receber valores TRUE e FALSE Indicador se o nó é um nó de informação ou é um cabeça de sub lista.Pode receber os valores REG (1), LST (2) e STK (3). Informação armazenada no nó Ponteiro para a sub lista encabeçada pelo nó Ponteiro para sucessor do nó na lista encadeada de nós Algoritmos das funções Procedimento sem tipo marca1 (sem parâmetros) Símbolo Tipo Escopo Descrição node “array” de global nós ou blocos de memória nodetype acc “array” de inteiros ponteiros externos para os nós imediatamente acessíveis NUMACC constante global número de nós acessíveis diretamente NUMNODES constante global número de nós disponíveis na memória REG constante global indicador de registro (1) LST constante global indicador de lista (2) TRUE constante global 1 FALSE constante global 0 i j inteiro inteiro local local ponteiro para o nó corrente ponteiro para o próximo nó a ser examinado Finalidade Efetuar a marcação dos nós em uso na memória. Nós em uso tem seu atributo marca ajustado para TRUE. Este algoritmo varre seqüêncialmente a memória. Início Para i variando de 0 até NUMACC node[acc[i]].marca TRUE Fim do Para /* até NUMACC i1 Enquanto i < NUMNODES /* do “array” acc seleciona-se o elemento i; do “array” node seleciona-se o elemento acc[i]; do registro selecionado por acc[i] seleciona-se o atributo next */ */ Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 12 / * i é o nó corrente * / j i + 1 / * próximo nó a ser examinado * / Se (node[i].marca) /* do “array” node seleciona-se o elemento i ; do registro selecionado por i seleciona-se o atributo marca */ então / * marcar os nós apontados por i * / Se (node[i].tipo= LST e node[node[i].info.lstinfo].marca TRUE) /* do “array” node seleciona-se o elemento i ; do registro selecionado por i seleciona-se o atributo tipo do registro node[i] seleciona-se o atributo info.lstinfo; do registro node[ node[i].info.lstinfo] seleciona-se o attibuto marca */ então / * A porção de informação de i aponta para um nó não marcado * / node[node[i].info.lstinfo].marca TRUE Se (node[i].info.lstinfo < j) /* do registro node[i] seleciona-se o atributo info.lstinfo */ então / * A busca prossegue na sucessão do menor endereço presente * / j node[i].info.lstinfo Fim do Se /* node[i].info.lstinfo < j */ Fim do Se /* node[i]. tipo= LST e node[node[i]. lstinfo].marca TRUE */ Se ( node[node[i].next].marca TRUE) /* do “array” node seleciona-se o registro node[i].next e, deste, seleciona-se o atributo marca */ então / * O nó sucessor de node [i] ainda não está marcado * / node[node[i].next].marca TRUE Se ( node[i].next< j) então j node[i].next Fim do Se /* node[i].next < j */ Fim do Se /* node[node[i].next] marca) TRUE */ Fim do Se /* node[i].marca */ ij Fim do Enquanto /* i < NUMNODES */ Fim do procedimento Procedimento sem tipo marca2 (sem parâmetros) Símbolo Tipo Escopo Descrição node “array” de global nós ou blocos de memória nodetype acc “array” de inteiros ponteiros externos para os nós imediatamente acessíveis NUMACC constante global número de nós acessíveis diretamente NUMNODES constante global número de nós disponíveis na memória REG constante global indicador de registro (1) LST constante global indicador de lista (2) TRUE constante global 1 FALSE constante global 0 s p pilha inteiro Procedimentos chamados local local pilha de inteiros ponteiro para o nó empty push pop Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 13 Finalidade Efetuar a marcação dos nós em uso na memória. Nós em uso tem seu atributo marca ajustado para TRUE. Este algoritmo percorre as lista em vez de varrer seqüêncialmente a memória. Quando é encontrado um nó de sub lista este nó é empilhado até a a conclusão do percurso na lista corrente. Sucessivamente são desempilhados os nós cabeças de sub listas para que estas possam ser percorridas e seus nós examinados para marcação ou não. Início Para i variando de 0 até NUMACC / * Marcar o próximo nó acessível e empilhá-lo * / node[acc[i]].marca TRUE /* do registro selecionado por acc[i] seleciona-se marca */ push (s, acc[i]) Enquanto ( empty (s) TRUE) pop (s, p) Enquanto (p 0) Se ( node[p].tipo = LST e node[node [p].info.lstinfo].marca TRUE) /* do “array” node seleciona-se o elemento p ; do registro selecionado por p seleciona-se o atributo tipo do registro node[p] seleciona-se o atributo info.lstinfo; do registro node[ node[p].info.lstinfo] seleciona-se o attibuto marca */ então node[node [p].info.lstinfo].marca TRUE push (s, node[p].lstinfo) Fim do Se Se (node[node [p].next].marca = TRUE) /* do “array” node seleciona-se o registro node[p].next e, deste, seleciona-se o atributo marca */ então /* o sucessor de p já está marcado */ p0 senão /* marcar o sucessor de p */ p node [p].next node [p].marca TRUE Fim do Se Fim do Enquanto /* end p 0 */ Fim do Enquanto /* end empty (stack) TRUE */ Fim do Para Fim do procedimento Procedimento sem tipo marca3 (sem parâmetros) Símbolo Tipo Escopo Descrição node “array” de global nós ou blocos de memória nodetype acc “array” de inteiros ponteiros externos para os nós imediatamente acessíveis NUMACC constante global número de nós acessíveis diretamente NUMNODES constante global número de nós disponíveis na memória REG constante global indicador de registro (1) LST constante global indicador de lista (2) STK constante global indicador de lista empilhada (3) TRUE constante global 1 FALSE constante global 0 Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 14 top p q again inteiro inteiro inteiro rótulo local local local local ponteiro para topo de pilha ponteiro para o nó corrente ponteiro para o nó sucessor do nó corrente ponto de retorno para analise de listas Finalidade Efetuar a marcação dos nós em uso na memória. Nós em uso tem seu atributo marca ajustado para TRUE. Este algoritmo utiliza como pilha de armazenamento dos cabeças das sub listas a própria lista. Isto é feito utilizando, temporariamente, os ponteiros existentes nos nós (lstinfo e next) para implementar a pilha. Após percorrer cada lista ou sub lista os atributos referenciados são atualizados para seus valores correntes. Início Para i variando de 0 até NUMACC - 1 / * Percorrer a lista de cada nó diretamente acessível * / p acc [i] / * inicializar pilha vazia * / top 0 again: / * Atravessar a lista pelos ponteiros next marcando cada nó e empilhando-o até encontrar um nó já marcado ou o final da lista. Supõe-se node [0].marca = TRUE */ Enquanto ((node[p].marca TRUE) e (node[p].next 0)) node[p].marca TRUE / * Empilhar node [p] armazenando em q um ponteiro para o próximo nó * / q node[p].next node[p].next top top p / * Avançar para o próximo nó * / pq Fim do Enquanto / * Neste ponto retornar através da lista desempilhando sucessivamente até alcançar um nó cujo atributo lstinfo aponte para um nó não marcado ou terminar a lista */ Enquanto ( top 0) / * Restaurar lstinfo ou next para p e desempilhar * / p top / * Restaurar o atributo correto de node [p] * / Se (node [p].tipo = STK) então /* no primeiro desempilhamento de seu atributo tipo foi alterado para STK e o nó foi reempilhado. Agora sua sub lista já foi percorrida */ node[p].tipo LST / * lstinfo foi usado como ponteiro da pilha. Restaurar o atributo tipo */ top node[top].info.lstinfo / * Desempilhar */ node [p].info.lstinfo q / * Restaurar o atributo lstinfo * / qp senão / * next foi usado como ponteiro para a pilha. Desempilhar * / top node[top].next / * Restaurar o atributo next * / node[p].next q qp / * Verificar se é necessário percorrer sub lista */ Se (node[p].tipo= LST) então / * lstinfo será usado como ponteiro da pilha * / Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 15 node[p].tipo STK q node [p].info.lstinfo node [p].info.lstinfo top / * Empilhar node [p] * / top p pq / * Avançar para o próximo nó * / goto again /* como para o mesmo valor de acc[i] existe outra lista a ser percorrida volta-se ao ponto de início da travessia de lista, sem desmontar a pilha */ Fim do Se /* node[p].tipo= LST */ Fim do Se / * node[p].tipo= STK * / Fim do Enquanto / * top 0 * / Fim do Para Fim do procedimento Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 16 Algoritmos para Compactação Na compactação de memória os nós marcados são compactados para as posições iniciais da memória. Além da modificação de endereços dos nós há necessidade de atualização dos ponteiros internos e externos para ajustamento aos novos endereços. Um problema que surge na compactação caracteriza-se pelo fato de, ao se atualizar os ponteiros de um nó relocado, não há como se conhecer os novos endereços dos nós por ele apontados se estes nós ainda não tiverem sido relocados. Resolve-se este problema efetuando-se duas passagens sobre a memória. Na primeira delas criam-se listas de ajustamento, sendo uma para cada nó apontado por qualquer outro, contendo todos os demais nós que apontam o nó proprietário da lista. As listas de ajustamento são encadeadas pelos mesmos ponteiros que apontavam o nó proprietário e que serão temporariamente modificados para que a lista possa ser criada. Na segunda passagem sobre a memória alocase novo endereço para cada nó, todos os ponteiros que mantém a lista de ajustamento desse nó recebem o novo endereço e só então move-se o nó para o novo endereço. Estrutura de um nó no processo de compactação Atributo marca É union ? Não Componentes - Tipo inteiro tipo Não - inteiro info Sim next Não reginfo lstinfo - reg inteiro inteiro header Não - inteiro headptr Não - inteiro infoptr Não - inteiro nextptr Não - inteiro Descrição Indicador se o nó foi marcado como estando em uso. Pode receber valores TRUE e FALSE Indicador se o nó é um nó de informação ou é um cabeça de sub lista.Pode receber os valores REG (1), LST (2) e STK (3). Informação armazenada no nó Ponteiro para a sub lista encabeçada pelo nó Ponteiro para sucessor do nó na lista encadeada de nós primeiro nó da lista de ajustamento ou lista de nós que apontam o nó corrente indicador de qual dos ponteiros do nó apontado pelo header é empregado para fazer a conexão à lista de ajustamento indicador de qual dos ponteiros do nó apontado por listinfo é empregado para fazer a conexão à lista de ajustamento indicador de qual dos ponteiros do nó apontado por next é empregado para fazer a conexão à lista de ajustamento Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 17 Algoritmos da função Procedimento sem tipo compacta (sem parâmetros) Símbolo Tipo Escopo Descrição node “array” de global nós ou blocos de memória nodetype i newloc nd p inteiro inteiro inteiro inteiro local local local local q inteiro local source caractere local ponteiro para nó ponteiro para novo endereço de um nó nó corrente em varredura seqüêncial ponteiro para o nó corrente de uma lista de ajustamento ponteiro para o nó sucessor do nó corrente em uma lista de ajustamento indicador de qual a fonte da ligação do nó à lista de ajustamento, sendo: ‘I’ para lstinfo ‘L’ para next Finalidade Efetuar a compactação da memória. Esta compactação deve ser precedida de uma marcação na qual os nós em uso tem seu atributo marca ajustado para TRUE. Na compactação os nós recebem novos endereços junto ao início da memória. Uma complicação adicional do processo advém da necessidade de atualização dos ponteiros que estejam presentes nos nós deslocados. É preciso percorrer duas vezes as listas de nós e utilizar listas auxiliares que contenham todos os nós que apontam cada nó da lista. Esytas listas auxiliares, cujo número máximo é igual ao número de nós presentes, são chamadas de listas de ajustamento. Início / * Inicialização de atributos * / Para i variando de 1 até MAXNODES - 1 node [i].header 0 node [i].headptr ‘N’ node [i].infoptr ‘N’ node[i].nextptr ‘N’ Fim do Para newloc 0 Para nd variando de 1 até MAXNODES - 1 / * Só se considera nós marcados * / Se (node[nd].marca = TRUE) então / * Nova posição para o nó * / newloc newloc + 1 /* para todos os nós que já foram encontrados apontando para nd neste passo, ajustar o ponteiro apropriado para que passe a apontar a nova localização de nd */ p node [nd].header / * cabeça de lista dos nós que apontam para nd * / source node[nd].headptr Enquanto (p 0) / * Percorrer a lista de nós, até agora encontrados, que apontam para nd * / Se ( source = ‘I’) então / * O nó está encadeado à lista de ajustamento pelo ponteiro lstinfo * / q node [p].info.lstinfo / * Sucessor de p na lista de ajustamento * / Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 18 source node[p].infoptr node [p].info.lstinfo newloc / * atualizar o ponteiro que anteriormente apontava nd* / node[p].infoptr ‘N’ / * O nó p não mais faz parte da lista ajustamento * / p q senão / * O nó está encadeado à lista de ajustamento pelo ponteiro next * / q node[p].next source node[p].nextptr node[p].next newloc node[p].nextptr ‘N’ p q Fim do Se / * source = ‘I’ */ Fim do Enquanto /*p0*/ / * Toda a lista de ajustamento já foi percorrida e ela torna-se desnecessária * / node [nd].headptr ‘N’ node [nd].header 0 /* Criação da lista de ajustamento */ / * Caso algum ponteiro de nd apontar para outro nó, como p, por exemplo. colocar nd na lista de ajustamento encabeçada por node [p].header */ Se (node [nd].tipo = LST e node [nd].info.lstinfo 0) então p node [nd].info.lstinfo / * p é o nó apontado por node [nd].info.lstinfo * / node [nd].info.lstinfo node [p].header node [nd].infoptr node [p].headptr node [p].header nd /* aquí nasce a lista de ajustamento */ node [p].headptr ‘I’ Fim do Se / * node [nd].tipo = LST * / senão p node [nd].next / * p é o nó apontado por node [nd].next * / node [nd].next node [p].header node [nd].nextptr node [p].headptr Se ( p 0) então node [p].header nd /* aquí nasce a lista de ajustamento */ node [p].headptr ‘L’ Fim do Se / * p 0 * / Fim do Se / * node [nd].marca = TRUE * / Fim do Para /* nd variando de 1 até MAXNODES - 1 */ / * A passagem agora concluída ajustou todos os ponteiros para nós com endereços superiores aos dos nós varridos e criou listas de ajustamento para os nós com ponteiros para nós com endereços inferiores aos varridos. Uma segunda passagem vai tratar as listas de ajustamento que restarem e mover os nós para suas novas posições. */ newloc 0 Para nd variando de 1 até MAXNODES - 1 Se (node[nd].marca) então newloc newloc + 1 p node [nd].header source node [nd].headptr Enquanto ( p 0) / * Percorrer a lista * / Se ( source = ‘I’) Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 19 então q node[p].info.lstinfo source node[p].infoptr node[p].info.lstinfo newloc node[p].infoptr ‘N’ pq senão q node [p].next source node [p].nextptr node [p].next newloc node [p].nextptr ‘N’ pq Fim do Se / * source = ‘I’ * / Fim do Enquanto / * p 0 * / node [nd].headptr ‘N’ node [nd].header 0 node [nd].marca FALSE node [newloc] node [nd] Fim do Se / * marca ([nd].node * / Fim do Para Fim do procedimento Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 20 9.3 - GERENCIAMENTO DINÂMICO DE MEMÓRIA Conceito A alocação e deslocação de memória, ou seja, a atribuição de trechos de memória a processos é feita em blocos de memória de tamanhos requisitados pelos respectivos processos. A natureza dinâmica dessas ocorrências faz com que, em geral a memória fique fragmentada intercalando blocos alocados e blocos livres. Esta fragmentação pode provocar a impossibilidade do atendimento de uma solicitação pela inexistência de um bloco livre no tamanho desejado muito embora o somatório das áreas livres fragmentadas supere o tamanho solicitado. Pode-se gerenciar tal problema de diversas maneiras. No processo de compactação considera-se disponível o espaço entre o final do último bloco de memória alocado e o final da memória. O início do espaço disponível é apontado por freepoint. Sempre que uma solicitação de memória ultrapassa o espaço disponível interrompe-se o processamento, compacta-se a memória, atualiza-se o ponteiro freepoint e verifica-se então a possibilidade de atendimento da solicitação. Outro processo consiste em manter os espaços liberados ou ainda não alocados em uma lista encadeada. Esta lista é apontada por freepoint e mantida, como fila de prioridades, em ordem crescente de endereço. No gerenciamento dinâmico de memória os nós podem ser alocados e desalocados um de cada vez. Contudo é frequente que surjam requisições de alocação de blocos de memória de tamanhos variáveis. Nestas situações os blocos liberados são mantidos, também, em listas encadeadas. O processo de alocação subsequente de blocos de memória pode ser feito de diversas maneiras, tais como: Primeiro ajuste Melhor ajuste Pior ajuste Primeiro Ajuste No método do primeiro ajuste a lista de blocos livres é percorrida sequencialmente até encontrar o primeiro bloco livre cujo tamanho seja maior ou igual do que a quantidade de memória requisitada. Melhor Ajuste O método do melhor ajuste busca o menor bloco livre cujo tamanho seja maior do que ou igual à quantidade de memória requisitada. Pior Ajuste O método do pior ajuste consiste na alocação de uma porção do maior bloco livre constante da lista de blocos livres. Usando um pequeno número de grandes blocos para satisfazer a maioria das requisições muitos blocos de tamanho moderado permanecem sem fragmentação. A não ser no caso da maioria de requisições de memória em grandes blocos, o método do pior ajuste satisfaz a um maior número de requisições do que os outros métodos. Exemplos 1) Considere-se a situação na qual existem blocos livres com tamanhos 200, 300 e 100 unidades de memória. Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 21 Deseja-se alocar blocos de memória com os tamanhos 150, 100, 125, 10 e 100 unidades. A seqüência de alocação, pelos diversos processos é a seguinte. Espaços Livres 200 300 100 150 200 150 100 Espaços Livres 200 300 100 100 0 25 0 150 50 300 100 Processo da Primeira Escolha Solicitações 100 125 100 100 50 50 50 Não pode 75 Não pode 200 75 100 100 0 Não pode 150 50 300 100 Processo da Melhor Escolha Solicitações 100 125 100 100 50 50 50 Não pode 300 175 75 Não pode 0 0 Não pode 0 Espaços Livres 200 300 100 Processo da Pior Escolha Solicitações 100 125 100 100 100 0 150 25 25 100 100 100 2) Considere-se a situação na qual existem blocos livres com tamanhos 110 e 54 unidades de memória. Deseja-se alocar blocos de memória com os tamanhos 25, 70 e 50 unidades. A seqüência de alocação, pelos diversos processos é a seguinte. Espaços Livres 110 54 Espaços Livres 110 54 Espaços Livres 110 54 Processo da Primeira Escolha Solicitações 25 70 50 15 85 15 54 54 4 Processo da Melhor Escolha Solicitações 25 70 50 110 40 Não pode 29 Não pode 29 Processo da Pior Escolha Solicitações 25 70 50 15 85 15 54 54 4 Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 22 O método do primeiro ajuste é mais eficiente se a lista de blocos disponíveis for mantida em ordem crescente de endereços de memória. Caso a lista seja mantida em ordem crescente de tamanho de bloco a busca por melhor ajuste se torna mais eficiente. Caso a lista seja mantida em ordem decrescente de tamanho de bloco o método do pior ajuste é o mais eficiente. Todavia não é prático manter a lista de blocos disponíveis classificada por tamanho. Na ausência de outras considerações ou especificações é usual se dar preferência ao método do primeiro ajuste. Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 23