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 @.

Exemplo 1: Obtendo o endereço de uma variável integer:

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.

Exemplo 2: Atribuindo endereços de variáveis a um apontador:

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>;

Exemplo 3: Crie um apontador para um Item que possui um nome e um preço:

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.

Exemplo 4: 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>)

Exemplo 5: Usando new e dispose para trabalhar com variáveis dinâmicas.

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.

 

 

Exemplo 6: Usando variáveis dinâmicas para ler e guardar na memória uma lista de itens.

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.

Exemplo 7: Usando GetMem, FreeMem, MaxAvail e MemAvail.

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