Apontadores (Ponteiros)
Um apontador é uma variável que contém o endereço de um dado ou de um código de programa. Podemos obter o endereço de uma variável usando a função Addr ou o operador @.
var N :
integer;
begin
writeln('Addr(N)=', longint(Addr(N)));
writeln('@N=', longint(@N))
end.
Variável tipo Pointer
Usa-se uma variável tipo Pointer para armazenar um endereço de memória.
var <nome-da-variável> : Pointer;
Este tipo de apontador pode ser usado para guardar o endereço de qualquer variável, independente do tipo da mesma.
var
N : integer;
R : real;
S : String[50];
P : Pointer;
begin
P := Addr(N); {1}
writeln('Addr(N)=', longint(P));
P := Addr(R); {2}
writeln('Addr(R)=', longint(P));
P := @S; {3}
writeln('Addr(S)=', longint(P));
P := @P; {4}
writeln('Addr(P)=', longint(P));
end.
Supondo que N, R, S e P estão juntos na memória, teríamos uma organização semelhante à abaixo:
Porém, as variáveis dinâmicas no Free Pascal são normalizadas para ocupar pedaços de memória de 16, 32 ou 64 bytes (o menor possível). Sendo assim, teríamos o resultado abaixo:
Apontadores Tipados
São apontadores que podem ser usados para referenciar um tipo específico. São declarados da seguinte forma:
var <nome-da-variável> : ^<tipo>;
type
TItem = record
Nome : String;
Preco : real;
end;
var
PItem : ^TItem;
Item : TItem;
begin
Item.Nome := 'Livro de Ed1';
Item.Preco := 25.0;
PItem := Addr(Item);
end.
Desreferenciando um Apontador
Desreferenciar um apontador faz com que obtenhamos o valor contido no endereço apontado.
<identificador>^
A atribuição de apontadores é igual a uma variável normal.
var
X : integer;
P1, P2 : ^integer;
begin
X := 10;
P1 := @X;
writeln(X);
writeln(P1^);
P2 := P1;
P2^ := 100;
writeln(X)
end.
A Constante Nil
É uma constante que representa um endereço de memória inválido, ou seja, um apontador com esse valor não aponta para nada.
Variáveis Dinâmicas
São variáveis que são criadas e destruídas durante a execução do programa (dinamicamente e sob demanda). Não são associados a um identificador. Não são declaradas na seção de declaração de variáveis. São sempre referenciadas através de apontadores.
Permitem melhor gerenciamento da memória, já que podem ser alocadas sob demanda e liberadas assim que não forem mais necessárias.
Suportam a criação de estruturas de dados complexas de forma eficiente, como listas encadeadas e árvores.
Em certas situações, são mais eficientes do que estruturas estáticas, permitindo que o programador decida qual é melhor em cada situação.
Porém, o uso de variáveis dinâmicas está associada a mais erros de programação, já que a alocação e desalocação de memória fica a cargo do programador.
Alocação e Desalocação de Variáveis Dinâmicas
Há uma função chamada new que dado um apontador cria uma variável dinâmica do tipo do apontador e faz com que o apontador tipado passado aponte para a variável criada.
new (<apontador-tipado>)
Para liberar a memória alocada pela função new, usa-se o procedimento dispose.
dispose (<apontador-tipado>)
type
TItem = record
Nome : String[20];
Preco : real
end;
var
PItem, PAnterior : ^TItem;
begin
New(PItem);
PItem^.Nome := 'Livro de ED1';
PItem^.Preco := 30.0;
PAnterior := PItem;
New(PItem);
PItem^.Nome := 'Outro livro';
PItem^.Preco := 10.0;
writeln(PAnterior^.Nome);
writeln(PAnterior^.Preco);
writeln(PItem^.Nome);
writeln(PItem^.Preco);
Dispose(PAnterior);
Dispose(PItem);
PItem := nil;
PAnterior := nil
end.
type
PItem = ^TItem;
TItem = record
Nome : string[20];
Preco : real;
Proximo : PItem
end;
var
Head, PNew, PAtual : PItem;
Resp : char;
begin
Head := nil;
repeat
New(PNew);
writeln('Digite o nome e o preco do Item:');
readln(PNew^.Nome, PNew^.Preco);
PNew^.Proximo := Head;
Head := PNew;
write('Outro Item (S/N)?');
readln(Resp);
until UpCase(Resp) = 'N';
writeln('Produtos
Lidos:');
PAtual := Head;
while PAtual <> nil do
begin
writeln(PAtual^.Nome, ' - ',
PAtual^.Preco);
PAtual := PAtual^.Proximo;
end
end.
Alocando e Desalocando Memória usando GetMem e FreeMem
GetMem(<apontador>, <número-de-bytes>)
FreeMem(<apontador>, <número-de-bytes>)
A Lista de Blocos Livres
Ao desalocar uma variável dinâmica, normalmente cria-se um espaço vazio na memória. O endereço e o tamanho desta variável são incluídos na lista de blocos livres. As funções New e GetMem verificam se há um bloco livre na lista com o tamanho desejado.
O uso desta lista de blocos livres permite que áreas de memória fornecidas pelo sistema operacional sejam reaproveitadas para outras variáveis dinâmicas sem que seja necessário alocar mais memória.
As funções MaxAvail e MemAvail
MaxAvail retorna o tamanho do maior bloco de memória livre, incluindo o bloco que começa no topo do heap.
MemAvail retorna a soma dos tamanhos de todos os blocos livres.
var
P1, P2 : Pointer;
begin
writeln('MaxAvail:', MaxAvail, ', MemAvail:', MemAvail);
GetMem(P1, 5000);
GetMem(P2, 1000);
writeln('MaxAvail:', MaxAvail, ', MemAvail:', MemAvail);
FreeMem(P1, 5000);
writeln('MaxAvail:', MaxAvail, ', MemAvail:', MemAvail);
FreeMem(P2, 1000);
writeln('MaxAvail:', MaxAvail, ', MemAvail:', MemAvail);
end.
As funções MaxAvail e MemAvail só funcionam no Turbo Pascal. Isto ocorre porque o Free Pascal é um compilador para sistemas operacionais modernos e que permitem vários processos em paralelo e não limita a quantidade de memória disponível para um programa, como ocorre com o DOS. Sendo assim, mesmo que as funções retornariam uma quantidade de memória que muito provavelmente já não seria válida porque outros processos estão alocando e desalocando memória em paralelo.
Desta forma, com o Free Pascal devemos tentar alocar a quantidade de memória e testar se tivemos sucesso, ou simplesmente não testar e deixar o programa encerrar por falta de memória.
Cuidados com o Dispose
Após executar Dispose o apontador continua apontado para o mesmo endereço
Erros Comuns com Apontadores
var
P1, P2, P3
: ^integer;
begin
{Situação 1}
new(P1); {Cria uma variável dinâmica e faz P1 apontar para
ela}
new(P2); {Cria outra variável dinâmica}
P1 := P2; {P1 e P2 passam a apontar para a segunda
variável criada. A primeira fica perdida na memória}
{Situação 2}
new(P3); {Cria a terceira variável dinâmica e P3 aponta
para ela}
new(P3); {Cria a quarta variável dinâmica e P3 aponta para
ela. Perde-se o endereço da terceira variável}
end.
A figura abaixo mostra o que acontece com a pilha e o heap durante a execução do exemplo acima. Observe que as variáveis "Var 1" e "Var 3" estão perdidas na memória, já que não se tem mais referências para ela a partir da pilha, seja diretamente ou indiretamente:
var
P1, P2
: ^integer;
begin
new(P1); {Cria uma variável dinâmica e faz P1 apontar para
ela}
readln(P1^);
writeln(P1^);
dispose(P1); {P1 já foi liberada da memória e entrou na
lista de blocos livres}
new(P2); {Cria outra variável dinâmica, podendo até reusar
o espaço desalocado de P1}
readln(P1^); {Continua usando P1 como se não tivesse desalocado,
podendo gerar erros de execução}
writeln(P1^);
end.
A figura abaixo mostra o que acontece com a pilha e o heap durante a execução do exemplo acima. Observe que após o dispose(P1), a variável dinâmica "Var 1" é liberada e passa a fazer parte do espaço livre. Porém, P1 continua apontando para esta área da memória, o que permite inclusive continuar acessando os dados lá armazenados. Logo sem seguinda, é o comando new(P2) aloca uma variável dinâmica "Var 2" que pode ser alocada no espaço antes destinado a "Var 1". Caso isso aconteça, P1 e P2 estarão apontando para a mesma variável, sendo possível alterar o valor da mesma a partir de 2 apontadores distintos. O pior é que estes apontadores podem ser de tipos diferentes, o que quebra o faz com que o Pascal não seja uma linguagem fortemente tipada.
Coleta Automática de Lixo
Deixar o gerenciamento de memória a cargo do programador, apesar de ser mais flexível e potencialmente mais eficiente (caso seja um ótimo programador), normalmente é associado a erros, que só ocorrem em determinadas situações e em tempo de execução.
Além disso, o vazamento de memória não provoca erros visívies ao usuário. Porém, faz com que o programa use mais memória do que o necessário, requerendo frequentes reinicializações do mesmo para liberá-la. Na maioria dos programas isto não é grave, mas em Sistemas Operacionais e Servidores (Web, Aplicação, Arquivos, por exemplo) é crítico porque demandaria muita memória ou reinicializações freqüentes.
Diante deste problema, as linguagens de programação mais modernas têm adotado a liberação automática de memória, conhecida como Coleta Automática de Lixo (Automatic Garbage Collection). Recebe este nome porque o programador não se preocupa em desalocar as variáveis dinâmicas usada, o que faz com que lixo (garbage) seja acumulado na memória. Mas nestas linguagens o ambiente (Máquina Virtual Java, por exemplo) varre a memória em busca deste lixo e o libera automaticamente.
É importante lembrar que mesmo linguagens que usam Coleta Automática de Lixo precisamos fazer com que os objetos deixem de ser referenciados (atribuindo null a uma referência Java). Caso contrário, não serão liberados. Por exemplo, ao utilizar um array para armazenar os objetos de uma lista seqüencial, podemos controlar através do tamanho quantos objetos estão armazenados no array. O restante é considerado lixo. Porém, para o coletor de lixo, como temos a referência para o array e este para os objetos, ele não é capaz de desalocá-los.
Existem vários algoritmos para detecar lixo (garbage), ou seja, objetos para os quais não temos mais referência. De forma bastante simplificada, a idéia é partir das referências estáticas e das contidas na pilha, seguindo as referências contidas em cada objeto atingido e incluindo na lista dos objetos atingíveis. Os que não foram atingidos, podem ser desalocados porque não temos como acessá-los.
Vantagens
Desvantagens