Comparação entre variáveis estáticas
e variáveis dinâmicas
Até o presente momento, lidamos
com variáveis que tiveram de ser criadas antes de se executar um programa. São
variáveis que existem o tempo todo, ou seja, são variáveis estáticas. Portanto,
a alocação de memória para esse tipo de variável é feita antes da execução do
programa. A grande desvantagem desse tipo de variável é o fato de que uma vez
criada, o espaço de memória que ela ocupa não pode mais ser alterado. As
variáveis dinâmicas podem ser criadas e ou destruídas durante a execução de um
programa, e esta, é a grande vantagem delas sobre as estáticas. As variáveis
dinâmicas podem ser obtidas através de um tipo pré-definido em Pascal, chamado
Pointer.
O Pointer ou apontador, como o
próprio nome diz, aponta para um local de memória onde está armazenada uma
variável.
O tipo Pointer
O procedimento para se declarar
uma variável do tipo Pointer é simples, senão vejamos:
Var
p : ^Integer;
Após esta declaração, teríamos
criado uma variável do tipo Pointer que ocupa 4 bytes (lembre-se que ela aponta
um endereço, e como sabemos, no IBM/PC, um endereço é formado pelo Segment e
pelo offset, cada um com 2 bytes) e que irá apontar uma variável do tipo
Integer. Eu utilizei como exemplo, o tipo Integer, mas poderia ser qualquer
outro tipo e até mesmo Records.
Até esse instante, não criamos a
tão famosa variável dinâmica, e sim uma variável do tipo Pointer, que irá
apontar o endereço de uma variável dinâmica do tipo Integer. Isto parece meio
complicado a princípio, mas aos poucos, iremos entender o funcionamento desse
novo tipo de variável.
E agora eu pergunto, para onde
está apontando a variável recém-criada chamada p ? Simplesmente para nenhum
lugar. E isto recebe o nome em Pascal de NIL. Quando escrevemos no meio de um
programa a declaração abaixo:
p := NIL;
Estamos querendo dizer que a
variável do tipo Pointer, chamada p, não está apontando para
nenhuma variável no momento. Sempre que criamos uma variável do tipo
Pointer, ela tem o valor inicial NIL.
Criação de variáveis dinâmicas
O próximo passo, é a criação de
uma variável dinâmica, para tanto, utilizamos a procedure New. Sua sintaxe é:
New(p);
Isto faz com que seja alocado um
espaço de memória, suficiente para armazenar uma variável do tipo associado a
p, no caso integer. Esse espaço de memória fica num local especial chamado
HEAP. No caso do IBM/PC, o HEAP é toda a memória não utilizada pelo sistema.
Portanto, a declaração New(p)
aloca um espaço de memória no HEAP, suficiente para armazenar uma variável do
tipo Integer e retorna o endereço inicial desta região de memória para a
variável p. Lembre-se que p é do tipo Pointer.
A grande questão agora é: Como
acessamos essa variável dinâmica? Através da seguinte simbologia:
p^
Está na hora de um exemplo para
esclarecer melhor as coisas:
Program
Exemplo;
Uses CRT;
Type
Ponteiro = ^Integer;
Var p : Ponteiro;
i : Integer;
(* p é uma variável do tipo Pointer que aponta para variáveis dinâmicas
do tipo integer *)
Begin
ClrScr;
If p = NIL Then Writeln('sim');
(* como p acabou de ser criada, ela näo deve estar apontando para algum
endereço, ou seja, seu valor inicial deve ser NIL. Para descobrirmos se isso é
verdadeiro, basta compará-la com NIL *)
New(p);
(* acabamos de criar uma variável dinâmica do tipo Integer, e seu
endereço foi colocado no Pointer p *)
p^:=100;
(* estamos atribuindo o valor 100 à variável dinâmica recém-criada *)
Writeln(p^);
i:=200;
p^:=i;
Writeln(p^); (* será
escrito 200 *)
(* A função addr(var) retorna o endereço da
variável var *)
p:=addr(i); (* o pointer contém agora o endereço da variável i *)
p^:=1000; (*
indiretamente estou atribuindo o valor 1000 à variável i *)
Writeln(i); (* será escrito 1000 *)
End.
Estruturas de dados com ponteiros
Suponha que você tenha que fazer
um programa que terá que ler uma certa quantidade indeterminada de registros do
teclado. Você não sabe se serão 10, 100 ou até 1000 registros. A princípio,
você poderia super-dimensionar um array, desde que seu computador tenha memória
suficiente, mas mesmo assim, corre-se o risco de, no futuro, termos que
redimensionar a matriz. Para um caso como este, podemos utilizar o conceito de variáveis
dinâmicas. Para tanto, devemos declarar um Pointer para uma variável,
cuja estrutura seja constituída de dois campos: um contendo o valor
propriamente dito que se quer armazenar e o outro apontando para a próxima
variável dinâmica.
Exemplo:
Program
Exemplo;
Uses CRT;
{Este programa lê registros com a estrutura
abaixo, até que se digite 'fim' quando é perguntado o nome da pessoa. Repare
que o programa tem a capacidade de ler um número ilimitado de registros sem a
preocupação de se definir um array e sua respectiva dimensão.}
Nome
: String[30];
Sexo
: Char;
Idade : Integer;
Altura: Real;
Type
Pessoa = Record
Nome : String[30];
Sexo : Char;
Idade : Integer;
Altura: Real;
End;
ponteiro = ^Pessoas;
Pessoas = Record
Valor : Pessoa;
Prox : Ponteiro;
End;
Var
p,prim : Ponteiro;
Procedure Linha;
Var i:integer;
Begin
For i:=1 to 80 do write('-')
End;
Begin
Prim:=nil;
ClrScr;
Repeat
Linha;
New(p);
Write('Nome da pessoa
-----> ');
Readln(p^.valor.Nome);
If (p^.valor.Nome<>'fim')
Then Begin
Write('Sexo
---------------> ');
Readln(p^.valor.Sexo);
Write('Idade
--------------> ');
Readln(p^.valor.Idade);
Write('Altura
-------------> ');
Readln(p^.valor.altura);
p^.Prox:=Prim;
Prim:=p;
End;
Until p^.valor.nome='fim';
ClrScr;
Linha;
p:=prim;
While p<>nil do
Begin
With p^.valor do
Writeln(nome:30,sexo:5,idade:5,altura:6:2);
p:=p^.prox;
End;
End.
Procedures para variáveis dinâmicas
Dispose
Esta procedure libera o espaço
ocupado pela variável em questão que deve ser do tipo Pointer. Ela não mexe com
o resto do HEAP. Sintaxe:
Dispose(Var);
Podemos dizer que Dispose é
contrário a New, pois esta aloca espaço no HEAP para determinado tipo de variável
enquanto Dispose libera este espaço.
Mark
e Release
Como vimos, as variáveis
dinâmicas são armazenadas num local de memória especial chamado de HEAP. Esse
trecho de memória funciona como se fosse uma pilha. E para controlar o topo da
pilha, o Turbo Pascal mantém um apontador. Nós podemos alterar o valor do
apontador do topo do HEAP. Não podemos esquecer que alterando-o valor deste
apontador, todas as variáveis dinâmicas que estiverem acima deste endereço
serão perdidas. A procedure que nos permite alterar o valor deste apontador é a
Release e sua sintaxe é:
Release(Var);
Onde Var deve ser uma variável do
tipo Pointer e que deve conter o endereço desejado, para se atribuir ao apontador
do topo do HEAP.
Já a procedure Mark nos permitem
atribuir, a uma variável do tipo Pointer, o valor atual do apontador do topo do
HEAP. Sintaxe:
Mark(Var);
Estas duas procedures em conjunto
nos permite controlar e liberar, quando desejarmos, um trecho de memória do
HEAP.
GetMem e FreeMem
Com a procedure New, podemos alocar espaço necessário no HEAP
somente para uma variável de determinado tipo. Com o par Mark e Release ou
Dispose, podemos liberar tal espaço no HEAP. Já, as procedures GetMem e
FreeMem, podemos alocar o número de bytes que desejarmos, sem estarmos presos a
um determinado tipo de variável.
Sintaxes:
GetMem(Var,i);
Onde Var é do tipo Pointer e i
Integer.
Após esta declaração, teríamos
alocado no HEAP, um espaço de memória no HEAP no tamanho de i bytes. O endereço
inicial desse trecho de memória é retornado em Var.
FreeMem(Var,i);
Esta procedure faz exatamente o
contrário da GetMem, ou seja, libera i bytes no HEAP a partir do endereço
armazenado em Var.
Functions para variáveis dinâmicas
MaxAvail
Retorna um número inteiro, que
corresponde ao número de parágrafos (conjunto de 16 bytes) livres disponíveis
no maior bloco de espaço contíguo no HEAP.
MemAvail
Retorna o número de parágrafos
disponíveis no HEAP.