• •

Propaganda
Ponteiros e alocação dinâmica
Tem um ditado que diz: Eu não sei, mas tenho o telefone de quem sabe. Trabalhar com ponteiros é
muito parecido com isto, mas o ditado fica assim: Eu não sei, mas tenho o endereço de quem sabe.
Alguns tipos de variáveis são chamadas de Ponteiro porque elas apontam para algum endereço de
memória. Isto aparentemente complica a vida do programador, pois além de tratar da informação é
necessário tratar também do endereço da informação. E não é só isto: basta uma variável ponteiro
fazendo referência a uma área que não lhe diz respeito, e na melhor das hipóteses a aplicação emitirá
uma Violação de acesso à memória. O lado bom é que sistemas operacionais modernos (para não dizer
decentes) seguram o tranco e não deixam você destruir outras aplicações ou ele próprio. Nos tempos do
DOS era muito comum ter que recorrer ao gabinete e disparar um reset via hardware.
Os novos tipos de dados do Object Pascal visam transformar ponteiros em algo mais simples, mais fácil
de usar, ainda mais útil, e por vezes são tratados de forma tão transparente que muitas pessoas passam
anos trabalhando com eles sem saber. Quer um bom exemplo? Ansi string (a string padrão do
compilador Delphi, do FPC em modo delphi ou com a diretiva $H habilitada) é um ponteiro para string
que é alocada e desalocada sozinha pelo compilador.
Segue alguns conceitos sobre o tema.
VARIÁVEIS ESTÁTICAS
São as variáveis da forma tradicional, que guardam a própria informação, e o compilador faz o
tratamento de onde esta informação será guardada (pode ser um registrador do processador ou parte da
pilha de chamadas). Não há preocupação com alocação nem liberação deste espaço por parte do
programador.
var
A: Longint;
{ A é uma variável do tipo Longint, ocupará 4 bytes, e a alocação deste espaço é
tratada pelo compilador }
ALOCAÇÃO DINÂMICA
Entende-se por alocação dinâmica a criação e destruição de espaço na memória, em tempo de execução,
para armazenamento e manipulação de informação. É de responsabilidade do programador desalocar
toda memória que vier a alocar durante a execução do seu aplicativo, a menos que o próprio compilador
faça isto. Também é dever do programador saber quando o compilador fará isto.
PONTEIROS
Entende-se por ponteiros os tipos de variáveis que apontam para uma área da memória. Ponteiros
podem apontar para:
•
Áreas com informação, tal como uma variável;
•
Uma rotina (procedure, function ou método);
•
Um endereço nulo.
Ponteiros - Áreas com informação
O ponteiro pode ser declarado como:
•
tipado - ele sabe qual o tipo de informação para o qual está apontando.
•
não tipado - ele não sabe para que tipo de dado está apontando. É necessário fazer um typecast para
recuperar a informação. Typecast será abordado adiante.
O operador @ (arroba) pode ser usado para atribuir o endereço de uma variável para um ponteiro. Ex.:
A := @B;
{ A recebe o endereço da variável B }
Ou ainda, um ponteiro pode receber o endereço de uma área de memória alocada especialmente para
ele:
{ A - aponta para uma variável do tipo Longint }
new(A);
{ É alocada uma área na memória, com um total de 4 bytes, e A está apontando para
esta nova área }
Neste último caso o programador será responsável por esta área alocada, e deverá desalocar antes de
encerrar a execução do seu programa. No primeiro caso isto não é necessário, e sequer deverá ser feito,
pois o endereço de memória pertence à variável B que é de responsabilidade do compilador.
Ponteiros - Uma rotina
É possível criar ponteiros especialmente preparados para receber o endereço de uma rotina. Neste caso,
um exemplo fala mais sobre a sua utilidade: na programação por eventos, o disparo de um click em um
botão gera um evento que deve ser interpretado por uma rotina. Uma variável OnClick possui o
endereço da rotina que fará o tratamento deste evento.
Ponteiros - Um endereço nulo
No Pascal é conhecido como nil. Utiliza-se associar um ponteiro à constante nil (A := nil) para que fique
claro que este ponteiro não está apontando para informação alguma. Desta forma, torna-se possível a
comparação:
if A = nil then
writeln('Não há informação')
else
writeln('A está apontando para alguma informação');
DECLARAÇÃO
Declara-se um ponteiro tipado informando o tipo da variável para o qual ele apontará, precedido pelo
operador ^ (circunflexo).
var
A: ^Longint; { A é uma variável ponteiro, e aponta para um Longint }
B: ^string; { B é uma variável ponteiro e aponta para uma string }
É possível também declarar um ponteiro não tipado, ou seja, que não se sabe exatamente para qual tipo
de informação ele irá apontar.
var
C: Pointer;
{ C é uma variável ponteiro, e pode apontar para qualquer tipo de dado }
Pode-se declarar também um ponteiro à uma procedure ou função.
var
D: procedure(Arg: Byte);
procedure rotina1(Arg: Byte);
begin
writeln('Rotina 1 recebeu ', Arg);
end;
procedure rotina2(Arg: Byte);
begin
writeln('Rotina 2 recebeu ', Arg);
end;
begin
D := @rotina2;
D(10); { Irá imprimir: 'Rotina 2 recebeu 10' }
end.
RECUPERAÇÃO DOS DADOS
Toda variável ponteiro (inclusive classes) possuem duas formas de se trabalhar - uma é com o endereço
para o qual ela aponta, e outra é para a informação que está no respectivo endereço. Exemplo:
var
A: ^Longint;
B: Longint;
begin
{ A linha abaixo faz com que A aponte para o endereço de B, é lida assim:
"A recebe o endereço de B" }
A := @B;
{ primeira forma - utilizando o endereço para o qual A está apontando }
{ A linha abaixo guarda o valor 5 na área de memória apontada por A, é lida assim:
"O endereço apontado por A recebe 5" }
A^ := 5;
{ segunda forma - utilizando a informação apontada por A }
writeln(B);
end.
Note a diferença no uso da variável. Sem circunflexo, é endereço. Com circunflexo, é a informação
apontada por aquele endereço.
TYPECAST
O typecast informa ao compilador o tipo de dado para o qual a variável ponteiro está apontado.
Há situações em que não é possível determinar o tipo exato de um ponteiro em tempo de projeto. Isto é
bastante comum em programação orientada a objetos, aonde o ponteiro (a variável de uma
determinada classe) pode apontar para uma série de objetos semelhantes, mas não iguais entre si.
Apenas para fins de esclarecimento, segue uma utilização do Typecast. Não é o exemplo mais comum,
mas é o mais simples de apresentar, explicar e entender:
var
A: Pointer;
B: Longint;
begin
A := @B;
B := 10;
writeln(Longint(A^));
end;
Recordando o exemplo anterior: colocar o circunflexo após a variável ponteiro significa trabalhar com
uma variável comum, que em seu conteúdo possui um número, uma string, etc. Isto é possível graças a
forma com que o ponteiro é declarado:
var
A: ^Longint; { Ponteiro para Longint }
begin
writeln(A^); { Imprime um Longint }
Mas ao criar ponteiros não tipados com o Pointer, o compilador fica sem saber com o que está
trabalhando quando o circunflexo é utilizado após a variável.
DESALOCANDO O QUE FOI ALOCADO
Tudo o que foi alocado pelo programador, e que não é controlado pelo compilador, deverá ser
desalocado logo que possível.
var
A: ^Longint;
begin
New(A); { Aloca uma área na memória para armazenar um Longint }
A^ := 10; { Coloca o valor 10 nessa nova área da memória }
Dispose(A); { Desaloca o espaço reservado pela procedure New }
end;
CONCLUSÃO
Estes tópicos abordam tudo o que diz respeito a ponteiros. Conforme colocado, existe mais assunto
relacionado devido aos novos tipos de dados do Object Pascal, tal como arrays dinâmicos, classes e
interfaces, ansi strings, entre outros. No entanto todos estes tipos trabalham sobre estes conceitos
recém abordados, o que muda é a interface do uso e a forma de alocar, acessar e desalocar esta área de
memória.
Download