Professional Documents
Culture Documents
Ponteiros
Porque precisamos e o que podemos fazer com eles
valores, mas apontam para uma rea da memria onde h um valor armazenado. Embora
esse assunto possa parecer muito acadmico, ainda muito utilizado para se aproveitar ao
mximo a API do Windows e at se desenvolver coisas novas, como novos componentes que
fazem uso da API. Veremos neste artigo o bsico de ponteiros, como eles podem ser usados
para se passar variveis por referncia e qual sua relao com vetores.
Mesmo nas linguagens de alto nvel como Delphi, ponteiros so o assunto de nvel mais
baixo que podemos tratar. Isso porque estaremos lidando diretamente com endereos da
memria. H quem defenda que ponteiros so coisa do passado e que no devem mais ser
usados. Mas isso um engano. Uma rpida olhada no cdigo-fonte da VCL e da RTL pode
revelar o tamanho uso que as prprias bibliotecas do Delphi fazem.
E mesmo em ambientes gerenciados como Python, Java, Ruby e .NET, embora no usemos
ponteiros diretamente ou explicitamente, a mquina virtual, geralmente escrita em C/C++, faz
um extenso uso de ponteiros. No prprio Delphi, todo objeto, cuja memria alocada
dinamicamente, um ponteiro, como veremos mais adiante.
Para armazenar valores ns declaramos variveis. Declarar uma varivel significa reservar
um espao na memria onde poderemos armazenar um valor qualquer. Agora imagine que
cada rea da memria contm um endereo numrico. Ponteiros trabalham diretamente com
os prprios endereos. Ou seja, voc pode acessar um valor atravs de seu endereo na
memria em vez de utilizar uma varivel.
No Delphi utilizamos o operador ^ (circunflexo) para declarar uma varivel do tipo ponteiro.
Usamos tambm o operador @ precedendo variveis normais para obter o endereo delas.
O inverso de @ seria usar ^ depois de uma varivel-ponteiro, para que possamos acessar o
seu valor. Um ponteiro recm declarado que no aponta para nenhum lugar tem o valor nil.
Antes de iniciarmos a nossa aplicao, atente para a Figura 1 que exibe um exemplo do
funcionamento de um ponteiro.
Vamos desenvolver uma aplicao Delphi e ver na prtica como tudo isso funciona (no Delphi
crie um projeto Win32).
Criaremos um boto para cada exemplo, nomeados como btEx<yy> onde yy ser o nmero
do nosso exemplo. Os captions de cada boto sero anlogos, como Ex 01, Ex 02 e assim
por diante.
Modifique a propriedade Name do formulrio para frmPonteiros e informe o Caption
Trabalhando com Ponteiros, por ltimo salve sua unit como uPonteiros.pas e seu projeto
como Ponteiros.dproj.
Declarando ponteiros
No nosso primeiro exemplo vamos declarar um ponteiro para integer. Isso significa que
teremos um endereo de memria apontando para um local onde ser armazenado um valor
inteiro. Para ver como funciona o mecanismo de ponteiros iremos declarar duas variveis do
tipo integer e obter o endereo de cada uma, e posteriormente o valor. No podemos
esquecer de inicializar o nosso ponteiro com nil ou j com um endereo vlido, caso contrrio
ele vir com um endereo randmico de memria, provavelmente apontando para algum lixo
ou at mesmo para a rea de instrues.
Nota
Para usar Watches clique em View > Debug Windows > Watches e a janela de Watches ir
aparecer. Essa janela muito importante para o debug porque com ela voc pode
visualizar os valores de variveis durante o processo de debug. Para isso, basta copiar e
colar uma varivel para dentro da janela ou, com um duplo clique na janela, escrever o
nome da varivel.
Na execuo dos mtodos mostraremos mensagens exibindo o valor das variveis em cada
etapa. Voc pode usar um memo para isso, ou, mais corretamente, uma Watch (janela do
debugger, veja Nota do DevMan).
Como no podemos declarar dentro dos parmetros de um mtodo um ponteiro usando
explicitamente o operador ^, declaramos o parmetro como sendo do tipo PInteger. O PInteger
nada mais do que um ponteiro para integer, definido em System.pas como:
PInteger = ^Integer;
Nota: Voc pode inclusive definir um Type, chamado PMyInteger = ^Integer.
Feito isso podemos usar, para alimentar esse parmetro, qualquer endereo de um integer
obtido com o operador @.
Isso muito importante caso se usem DLLs feitas em outras linguagens, onde sero
passados valores esperando que sejam modificados. A passagem de ponteiros por referncia,
usando-se a palavra reservada var, pode ser perigosa, pois cria na verdade uma referncia
duplamente indireta, ou um ponteiro para ponteiro.
Isso pode permitir que os endereos de alguns ponteiros sejam trocados, fazendo com que
apontem para outros valores, regies invlidas da memria ou para lugar nenhum.
Como era esperado o mtodo IncrementaPorValor incrementar a varivel y apenas dentro do
mtodo, e no fora dele.
Ao se trabalhar com estruturas de dados muito grandes, ou grandes vetores e matrizes, a
passagem de parmetros por referncia muito mais rpida, pois os dados no so copiados
para uma varivel interna do mtodo. Mas assegure-se de fazer isso apenas em
procedimentos que no alterem o valor ou valores da estrutura.
No estudo ou desenvolvimento de uma linguagem muito importante saber sobre o efeito
das palavras reservadas usadas nos argumentos de mtodos, e como esses argumentos so
empilhados, onde a memria alocada etc. Isso o funcionamento interno de qualquer
linguagem que venhamos a trabalhar.
Nota
Por isso nossos mtodos no devem aceitar objetos por referncia, a menos que eles
sejam criados ou destrudos por esses mtodos, como no caso do mtodo FreeAndNil, que
alm de destruir um objeto atribui nil a sua varivel.Como veremos mais adiante, esse o
mistrio envolvendo os objetos: suas variveis so ponteiros e apontam para um objeto
criado na memria. Porm nada, nenhuma varivel, o objeto propriamente dito.
Ponteiros e arrays
Arrays no Delphi nada mais so do que ponteiros para um primeiro elemento de uma srie de
tamanho finita. Para provar isso vamos declarar um array esttico global, de 10 posies, do
tipo integer. Os cdigos da Listagem 4 e Listagem 5 mostraro como o ponteiro para um array
na verdade o ponteiro para o primeiro elemento deste e mostra como podemos acessar os
elementos de um array atravs de seu ponteiro.
Tambm veremos um pouco sobre aritmtica de ponteiros. Podemos incrementar,
decrementar ou somar valores em ponteiros para avanar ou retroceder n elementos na
memria. As procedures inc e dec podem ser teis para incrementar ou decrementar um
ponteiro. Essas procedures j verificam de que tipo o ponteiro e somam o nmero de bytes
necessrios para avanar ou retroceder um elemento.
Isso importante, porque embora possamos converter um ponteiro para um integer, no
basta apenas somar ou subtrair unidades para avanar ou retroceder elementos.
Deve-se somar ou subtrair o nmero de bytes correspondente ao tamanho do tipo de dado
que estamos manipulando. Por exemplo, se estivermos trabalhando com byte podemos somar
ou subtrair 1.
Listagem 4. Relao entre ponteiros e vetores
var
frmPonteiros: TfrmPonteiros;
vetor: array[1..10] of integer;
implementation
procedure InicializaVetor();
var
I: Integer;
begin
//preencheremos os 10 valores do Vetor
//com a tabuada do 5, de 5 a 50
for I := 1 to 10 do
vetor[I] := i*5;
end;
procedure TfrmPonteiros.btEx04Click
(Sender: TObject);
var
p: PInteger;
begin
InicializaVetor();
p := @vetor;
showmessage(inttostr(p^));
//elemento 1 - valor 5
inc(p);
showmessage(inttostr(p^));
//elemento 2 - valor 10
inc(p, 2);
showmessage(inttostr(p^));
//elemento 4 - valor 20
p := pointer(integer
(p)+SizeOf(integer));
showmessage(inttostr(p^));
//elemento 5 - valor 25
p := pointer(integer(p)+SizeOf
(integer)*2);
showmessage(inttostr(p^));
//elemento 7 - valor 35
p := pointer(integer(p)+SizeOf
(integer)*5);
showmessage(inttostr(p^));
//elemento 12 - valor ???
end;
Nota
Nunca assuma que o tamanho de um tipo, em bytes, conhecido. Use sempre a funo
SizeOf para retornar o tamanho e multiplicar pelo nmero de elementos que voc quer
avanar ou retroceder. Por exemplo, em verses antigas do Delphi / Pascal, um integer
tinha 16 bits, o mesmo integer hoje tem 32 bits.
Nota
Arrays multidimensionais so o que comumente conhecemos pelo conceito de Matrizes. Ao
contrrio dos arrays comuns (vetores) que possuem apenas uma direo e acesso
simplificado atravs de um ndice, determinados pela linha, os arrays multidimensionais
trabalham com linhas e colunas, neste caso necessitando de mais de um ndice para
acesso de seus valores.
Isso faz com que convertamos p para integer e somemos o valor do tamanho de um integer
multiplicado pelo nmero de elementos que desejamos navegar. O inteiro resultante ns
convertemos de volta para ponteiro.
muito importante no somar ao ponteiro nmeros quebrados. Por exemplo, se estamos
trabalhando com integers no podemos somar 1 ao ponteiro. Isso far com que o ponteiro se
desloque apenas um byte e tente obter um valor inteiro de uma regio na memria que pega o
byte mais significativo de um inteiro com os trs bytes menos significativos de outro. Isso
resultar num nmero enorme, e totalmente incorreto.Temos outros tipos de arrays que
tambm devem ser observados. Os arrays multidimensionais, ou matrizes, so
multidimensionais apenas do ponto de vista do programador.
Do ponto de vista do sistema todos os arrays tm uma nica dimenso porque a memria tem
uma nica dimenso. A memria um gigantesco array unidimensional. Na Listagem 5 vamos
criar um array de duas dimenses e percorr-lo atravs de um ponteiro.
Na verdade a capacidade desse tipo de string no realmente infinita, mas o mximo nmero
de caracteres que ela pode ter corresponde ao mximo de um inteiro sem sinal. Um pouco
mais de quatro bilhes de caracteres.
Nas ltimas verses do Delphi as strings padro passaram a ser strings Unicode de dois bytes.
Ainda existem as strings de caracteres de um byte. So as AnsiStrings, formadas por
AnsiChars. PChar (PAnsiChar no nosso caso) muito usado para se comunicar com a API do
Windows porque trata-se de uma string aberta terminada em #0 (como no C/C++).
Na linguagem C uma string na verdade um ponteiro para char, criando um vetor de
tamanho indeterminado. Como fazer para saber onde a string termina e qual o seu tamanho?
As strings no C so terminadas por um caractere nulo, #0 para ASCII ou #0#0 para Unicode.
Esses ponteiros devem ter a sua memria alocada dinamicamente e o ltimo caractere deve
ser um #0. H uma outra maneira de criar strings desse tipo que usando arrays de chars.
Na Listagem 6 e Listagem 7 vamos criar shortstrings, que so aquelas strings que usvamos
antigamente no tempo do Pascal, com tamanho limitado. Usaremos tambm vetores de Char e
ponteiros e faremos chamadas a alguns mtodos da API do Windows.
Listagem 6. Relao entre ponteiros e shortstrings
procedure TfrmPonteiros.btEx06Click
(Sender: TObject);
var
//shortstrings j so formadas
//por AnsiChar, por padro
UmaString: string[20];
//como o novo Char agora WideChar,
//usamos AnsiChar explicitamente
UmVetor: array[1..20] of AnsiChar;
h: HWND;
PonteiroChar: PAnsiChar;
//Equivale a ^AnsiChar
begin
UmaString := ClubeDelphi;
UmVetor[1] := D;
UmVetor[2] := e;
UmVetor[3] := v;
UmVetor[4] := M;
UmVetor[5] := e;
UmVetor[6] := d;
UmVetor[7] := i;
UmVetor[8] := a;
UmVetor[9] := #0;
h := FindWindowA
(nil,Trabalhando com Ponteiros);
//teste tambm com Calculadora
SetWindowTextA(h, UmVetor);
ShowMessage(Repare que o caption
desta janela mudou para: +
AnsiString(UmVetor) );
SetWindowTextA(h, UmaString);
ShowMessage(Repare que o caption
desta janela mudou para: +
UmaString ); PonteiroChar := @UmVetor;
SetWindowTextA(h, PonteiroChar);
ShowMessage(Repare que o caption
desta janela mudou para: +
PonteiroChar );
end;
O que fizemos na Listagem 6 foi criar uma ShortString (string de tamanho definido) de 20
caracteres com o contedo ClubeDelphi. Criamos tambm um vetor de chars com os nove
primeiros elementos formando a palavra DevMedia e terminando com um #0. Mostramos como
podemos usar livremente tanto a string quanto o vetor ao chamar a funo SetWindowTextA
da API do Windows. Essa funo muda o texto de uma janela encontrada, dado o seu handle.
Encontramos o handle de nossa janela com a funo FindWindowA e passando como
argumento o Caption do nosso prprio form. Podemos tambm usar o ponteiro para o vetor.
Nota
Handle, na API do Windows, um nmero que aponta para uma janela, controle, arquivo,
objeto. Imagine um Handle como se fosse um tipo TObject do Delphi, ou Pointer, ou seja,
pode apontar para qualquer coisa.
Uma questo interessante que embora o tipo PAnsiChar seja definido como sendo
^AnsiChar, voc no pode criar um ponteiro do tipo ^AnsiChar e us-lo no mtodo
SetWindowTextA, porque ela foi definida, em seu cabealho, como usando PAnsiChar, e o
Delphi considera PAnsiChar um tipo, e controla rigidamente isso.
Listagem 7. Obtendo o primeiro byte e o valor armazenado nele
procedure TfrmPonteiros.btEx07Click
(Sender: TObject);
var
UmaString: string[20];
comprimento: integer;
begin
UmaString := ClubeDelphi;
comprimento := PByte(@UmaString)^;
ShowMessage
(Comprimento usando o primeiro byte: +
IntToStr(comprimento));
ShowMessage
(Comprimento usando Length:
+ IntToStr(Length(UmaString)));
UmaString := *Clube Delphi*;
comprimento := PByte(@UmaString)^;
ShowMessage
(Comprimento usando o primeiro byte: +
IntToStr(comprimento));
ShowMessage
(Comprimento usando Length:
+ IntToStr(Length(UmaString)));
UmaString := Delphi;
comprimento := PByte(@UmaString)^;
ShowMessage
(Comprimento usando o primeiro byte: +
IntToStr(comprimento));
ShowMessage
(Comprimento usando Length:
+ IntToStr(Length(UmaString)));
ShowMessage
(Tamanho total usando SizeOf: +
IntToStr(SizeOf(UmaString)));
end;
Tudo o que testamos aqui para AnsiChar e PAnsiChar vale tambm para Char e PChar, mas
como no Delphi o tipo Char agora Unicode, a API usada deveria ser SetWindowTextW ou
apenas seu alias SetWindowText, sem o A no final, que corresponde a AnsiChar.
Um aspecto interessante sobre as ShortStrings, ou strings de tamanho fixo, que antes do
primeiro char, em sua posio zero, armazenado o tamanho da string. O Delphi gerencia
automaticamente a cpia de strings, a limitao do tamanho e a concatenao do #0 no final.
Quando usamos, concatenamos, exibimos ou atribumos strings o Delphi conta e armazena a
quantidade de caracteres no primeiro byte (ou Word) e armazena os caracteres nas posies
subsequentes. O compilador faz com que esse primeiro byte fique invisvel para ns e opera
apenas com os posteriores. Precisamos de ponteiros para ver o primeiro byte.
Obter o tamanho de uma string assim mais rpido do que contar seus caracteres at
encontrar um #0, e assim que a funo Length funciona internamente. Inclusive, o valor que
voc vai obter nesse byte corresponde ao que a funo Length retornaria.
Para provar que uma string desse tipo um vetor, usamos a funo SizeOf, para verificar seu
tamanho em bytes, e a funo retorna 21, porque a string tem 20 caracteres (bytes) mais o
primeiro byte que o contador de caracteres.
O cdigo da Listagem 7 mostrar como obter o tamanho de uma string usando seu endereo,
apontando para o primeiro elemento e convertendo para byte. Usamos aqui um PByte que
nada mais do que um ^Byte.
Cada vez que alteramos a string obtemos novamente o valor armazenado no primeiro byte,
apontado pelo prprio endereo da string. Uma outra maneira de se fazer isso como
Strings comuns
As strings comuns so uma estrutura de dados muito especial que o Delphi trata de
maneira nica. Primeiro de tudo as strings normais so todas ponteiros. Voc no pode
simplesmente medir o tamanho delas, apenas o comprimento.
Enquanto as strings de tamanho fixo j tm seu tamanho mximo alocado na Stack, as strings
de tamanho varivel so alocadas dinamicamente, conforme necessrio. Alm disso, os oito
bytes anteriores ao primeiro caractere, ou seja, offset do -7 ao 0 podem ser divididos entre
dois integers de quatro bytes cada um. Um deles armazena a quantidade de caracteres, ou
seja, seu comprimento. O outro armazena uma contagem de referncias.
[abrir im age m e m jane la]
No vamos nos preocupar com o cdigo da Listagem 9, porque ele no representa realmente
a estrutura das strings, apenas uma ilustrao ou metfora de como seria. No mundo real a
parte de caracteres seria apenas um ponteiro para uma outra estrutura que pudesse
armazenar MaxInt-8 caracteres.
Isso porque como j dissemos, as strings no tm um tamanho de dois bilhes de bytes. Esse
seu tamanho mximo, porm o seu tamanho atual pode aumentar mediante alocao
dinmica de memria, gerenciada automaticamente pelo Delphi.
O -8 est aqui justamente porque os primeiros 8 bytes so reservados para o contador de
referncias e o de caracteres, respectivamente.
No cdigo da Listagem 10 vamos explorar esses contadores de referncias e de caracteres e
vamos mostrar como uma string j um ponteiro, tratado de maneira diferente pelo Delphi.
Na Listagem 10 criamos cinco strings. Trs delas tiveram valores constantes, strClube :=
Clube, strDelphi := Delphi e strTitulo := - Ponteiros. J a StringA formada dinamicamente,
em RunTime, pela concatenao dessas trs.
Listagem 9. Representao de como seria a estrutura de uma string
TEstruturaString = record
Referencias: integer;
Comprimento: integer;
Caracteres: array[1..MaxInt-8] of Char;
end;
Isso importante porque para uma string com valor constante o contador de referncias
sempre -1, que um valor reservado especial do Delphi. Sempre que uma string uma
constante, vem de uma constante ou de uma constante literal, como no caso strClube :=
Clube, o valor do contador de referncias ser -1. Se quiser pode fazer o teste com a
strClube.
Ao concatenar as trs strings atribuindo o resultado dinmico em StringA, esta passa a ficar
com 23 caracteres, e o contador de referncias passa a ser 1, j que tem apenas uma
referncia a esse valor.
A cada iterao atualizamos os ponteiros para ter certeza de que estamos apontando para o
endereo correto, j que o Delphi pode remanejar os endereos e tamanhos ocupados pelas
strings.Quando fazemos StringB := StringA as duas passam a ser exatamente idnticas. Por
esse motivo, no so copiados os valores dos 23 caracteres para a StringB. A StringB passa
a apontar para o mesmo endereo de memria que a string A e seu contador de referncias
incrementado para 2, j que agora existem duas strings apontando para o mesmo endereo.
Quando modificamos qualquer uma das duas, o contador de referncias volta a diminuir e os
caracteres so atualizados.
Tudo isso faz com que o Delphi tenha uma excelente performance ao lidar com
concatenaes de strings, superando em muito as linguagens gerenciadas, mas com um
recurso de contagem de referncias e alocao dinmica de strings que voc s encontraria
em linguagens gerenciadas/interpretadas.
Isso torna o Delphi robusto e rpido, e muito mais fcil de usar do que o C/C++ onde a
alocao de espao em memria para as strings manual, alm das cpias serem feitas
atravs de mtodos globais de bibliotecas, e todos os caracteres serem copiados.
Para se ter uma ideia de como o Delphi eficiente, comparando com o ambiente .Net, no .Net
Framework 1.1, por exemplo, cada concatenao de strings resulta na destruio de uma
string e na criao de outra, com o contedo completo. Isso resulta num grande overhead
quando se estiver criando strings grandes, como arquivos XML/HTML ou grandes textos.
Tanto que para textos grandes a Microsoft recomenda usar-se a classe StringBuilder em vez
de strings (algo como a TStringList da VCL). A concatenao de strings dentro de um loop, no
.Net, seria realmente muito mais lenta que usando o Delphi.
Nota
Se voc tentar usar o mtodo SizeOf em uma string comum (de tamanho varivel)
receber como resposta apenas o valor 4. Isso ocorre porque uma string um ponteiro,
possuindo apenas 4 bytes.
Essa a maneira recomendada de se fazer isso, mas mesmo que voc defina estaticamente
os cabealhos ou interfaces das funes dentro das DLLs voc pode criar um conjunto seu
de funes, que so na verdade ponteiros para funes com uma determinada assinatura.
Usando esses ponteiros voc pode, em uma sesso initialization, dizer quais mtodos reais os
ponteiros apontaro.
A Listagem 11 mostra como isso ficar na prtica. Criaremos algumas procedures e functions
para substituir outras muito conhecidas do Delphi, e criaremos alguns Types que sero na
verdade mtodos estticos globais, ou seja, procedures e functions.
Voc pode estar se perguntando por que no usamos o operador @ antes dos nomes das
funes para passar a referncia a elas para a varivel ExibeMsg. Na verdade isso seria o
correto. Se voc experimentar fazer esse cdigo em uma verso antiga do Delphi, como o
Delphi 3 por exemplo, voc ser obrigado a usar o operador @. A mesma coisa acontecer se
voc estiver usando o Lazarus.
Isso foi introduzido posteriormente no Delphi para facilitar esse tipo de atribuies indiretas
de mtodos. Algumas outras facilidades foram introduzidas no Delphi para a manipulao de
ponteiros, mas no citaremos essas, pois elas, embora tenham simplificado a sintaxe,
afastaram um pouco a linguagem dos padres do Pascal. Se voc desejar fazer um teste,
coloque os operadores @ antes dos nomes dos mtodos e ver que funcionar normalmente.
O que fizemos na Listagem 11 foi declarar uma varivel do tipo TMostraMensagem, que
definida por:
TMostraMensagem = procedure(const mensagem: string);
E atribumos a esta varivel duas procedures com a mesma assinatura, e que fazem
praticamente a mesma coisa.
Uma a ShowMessage original do Delphi, da unit Dialogs, e outra que ns mesmos criamos,
que internamente usa MessageBox para criar uma mensagem com cone e ttulo.
Desde a verso 2009, o Delphi tem uma maneira elegante de criar referncias a mtodos,
que so chamados de mtodos annimos. Eles podem ser usados em conjunto com ponteiros
para mtodos, conforme a Listagem 12. As palavras chaves reference to tambm podem ser
usadas para criar uma referncia (ponteiro) para mtodo. O que vemos adicionalmente um
mtodo annimo criado dentro do cdigo do evento OnClick do boto.
O cdigo da Listagem 12 praticamente idntico ao cdigo da Listagem 11, exceto pelo tipo
que declaramos e usamos:
TMostraMensagemAnonima = reference to procedure(const mensagem: string)
Com esse cdigo criamos um tipo que um mtodo e podemos tanto usar referncias
(ponteiros) a mtodos como criar mtodos InLine, os novos mtodos annimos do Delphi. Com
isso tambm conseguimos mostrar que vrios tipos de dados do Delphi so na verdade
ponteiros. A ltima relao que mostraremos ser a relao entre ponteiros e objetos.
Listagem 11. Ponteiros para mtodos
private
{ Private declarations }
public
{ Public declarations }
end;
TMostraMensagem = procedure
(const mensagem: string);
var(...)
procedure MensagemPersonalizada
(const mensagem: string);
begin
MessageBox(Application.Handle,
pchar(mensagem)
, Mensagem: , MB_OK or
MB_ICONERROR);
end;
procedure TfrmPonteiros.btEx09Click
(Sender: TObject);
var
ExibeMsg: TMostraMensagem;
mensagem: string;
begin
mensagem := Hello World;
//ligando a varivel ExibeMsg
//com a MensagemPersonalizada
ExibeMsg := MensagemPersonalizada;
ExibeMsg(mensagem);
//ligando a varivel ExibeMsg
//com a MensagemPersonalizada
ExibeMsg := ShowMessage;
ExibeMsg(mensagem);
end;
vrias classes com propriedades Read-Only, por exemplo, o objeto Singleton Screen, da
classe TScreen contm a propriedade Pixels Per Inch que Read-Only, definida internamente
dentro de sua unit, atravs da chamada de uma funo da API do Windows.
Tentar mudar o valor de uma propriedade somente leitura seria um erro de compilao.
Porm, e se consegussemos achar um ponteiro para a propriedade, ou pelo menos para os
Fields privados internos da classe? Podemos criar um ponteiro com endereo inicial do prprio
objeto e ir somando nmeros ao endereo desse ponteiro para vasculhar onde nosso
objeto armazena os valores das propriedades.
Nota
Na verdade, se pararmos para pensar, no existe nada somente leitura na memria de um
computador.
Crie uma nova unit chamada uPessoa.pas e implemente nessa unit o cdigo da Listagem 14.
Faremos uma classe TPessoa simples, com as propriedades somente leitura Nome, Idade e
Peso.
Uma peculiaridade dessa classe que todas as propriedades so somente leitura e seus
valores so definidos no corpo do construtor. Ao criarmos um objeto desse tipo, uma vez
passados os valores, no poderemos mais alterar suas propriedades. O que vamos fazer na
Listagem 15 ser criar um ponteiro para cada propriedade, do mesmo tipo que ela, e alterar as
propriedades atravs dos ponteiros. Quebramos assim o conceito de encapsulamento de uma
classe do Delphi e expomos seus atributos internos. No se esquea de adicionar a nova unit
ao uses do frmPonteiros.
Listagem 13. Um objeto um ponteiro
procedure TfrmPonteiros.btEx11Click
(Sender: TObject);
var
pessoa: TPessoa;
begin
pessoa := TPessoa.Create
(Ken Masters, 28, 85);
ShowMessage(O tamanho do objeto, embora tendo
uma string, um integer e um double :
+ IntToStr(SizeOf(pessoa)));
ShowMessage(O tamanho real do
objeto, em bytes ocupados no heap : +
IntToStr(pessoa.InstanceSize));
pessoa.Free;
end;
constructor TPessoa.Create
(aNome: string; aIdade:
integer; aPeso: double);
begin
FNome := aNome;
FIdade := aIdade;
FPeso := aPeso;
end;
end.
Criamos uma instncia do objeto TPessoa, com o nome Ken Masters, idade 25 e peso 85.
Nada de muito extraordinrio. Criamos trs ponteiros, um para integer, um para string e outro
para double. No precisamos definir esses ponteiros, ou tipos, nem declar-los com o sinal de
^, simplesmente porque o Delphi j define seus tipos da seguinte maneira, na unit System.pas:
Type
(...)
PInteger
PDouble
= ^Integer;
= ^Double;
PString = PUnicodeString;
PUnicodeString = ^UnicodeString;
Com os ponteiros que criamos pudemos alterar esses valores somente leitura e alterar o
valor das propriedades.
Essa tcnica que usamos no recomendada, pois pode criar efeitos colaterais indesejados
em cascata. Fizemos essa experincia apenas como prova de conceito.
Mesmo assim, se voc tiver um daqueles componentes ou bibliotecas de terceiros, antigo, de
uma empresa que j fechou, sem fontes e sem suporte e desejar alterar alguma classe ou
propriedade, pode criar um descendente que se utilize dessa tcnica. Voc pode testar isso
removendo do projeto a unit uPessoa.pas e renomeando essa unit, mas mantendo no diretrio
do projeto a uPessoa.dcu compilada. Deixo a cargo do leitor fazer os testes em classes nas
quais as propriedades so lidas de um mtodo acessor "get"
Nota
Voc dever criar um descendente para sua classe, que promova as propriedades
somente leitura para published, para poder usar RTTI. Na seo de links voc encontrar
um post antigo do blog de Hallvard Vassbotn, colunista e editor tcnico da revista inglesa
The Delphi Magazine e autor de vrios livros.
Alocao de memria
No poderamos falar sobre ponteiros sem falar sobre a alocao dinmica de memria.
Primeiro, um aspecto terico, mas importante, que existem vrias reas de memria: a rea
Data onde ficam alocadas nossas variveis e constantes globais, se que ns ainda as
usamos. A rea Stack onde ficam armazenadas chamadas a mtodos, parmetros dos
mtodos e variveis ou arrays internos do escopo de mtodos.
1 - Sempre que alocar memria use, dentro de um finally, o mtodo correspondente para
desalocar memria, caso contrrio criar um vazamento de memria;
2 - Sempre que alocar memria para uma varivel usada por um mtodo em uma DLL, caso
aloque memria fora da DLL, desaloque tambm fora da DLL e vice-versa;
3 - Sempre que precisar passar Pchars para DLLs que apenas leem ou usam a string
passada, pode-se passar uma constante ou uma string convertida para Pchar, mas sempre
que precisar que o contedo de uma string seja modificado dentro de uma DLL e devolvido ao
Delphi, no use functions, e aloque e desaloque a memria necessria fora da DLL.
Na Listagem 16 mostramos algumas das diferentes maneiras de se alocar memria.
Na Listagem 16 podemos ver que memria alocada com new deve ser desalocada com
dispose, memria alocada com GetMem deve ser desalocada com FreeMem e memria
alocada com GetMemory (que retorna ao endereo do ponteiro) deve ser desalocada com
FreeMemory.
Existem ainda outras funes da API do Windows, como HeapAlloc e VirtualAlloc para alocar
memria no heap e na memria virtual, respectivamente. Esses casos ns no estudaremos
nesse artigo.
Cada funo para alocar memria tem sua peculiaridade. Por exemplo, new serve somente
para alocar um nico espao para um nico valor do tipo desejado, no caso do integer, 4
bytes. GetMem pode ser usado para alocar, por exemplo, 20 bytes criando assim um vetor
para 5 inteiros. importante estudar a alocao e destruio de memria para utilizar
bibliotecas de terceiros, escritas em outras linguagens, principalmente se forem bibliotecas
que controlam dispositivos de hardware especficos.
Listagem 16. Alocao dinmica de memria
procedure TfrmPonteiros.btEx13Click
(Sender: TObject);
var
a,b,e: ^integer;
begin
try
new(a);
GetMem(b, sizeof(integer));
e := GetMemory(sizeof(integer));
a^ := 5;
b^ := 10;
e^ := 100;
ShowMessage(IntToStr(a^));
ShowMessage(IntToStr(b^));
ShowMessage(IntToStr(e^));
finally
dispose(a);
FreeMem(b);
FreeMemory(e);
end;
end;
Concluso
Embora ponteiros seja um assunto bastante acadmico, e considerado at mesmo de baixo
nvel, este um assunto essencial caso se deseje interagir com a API do Windows ou com
outras APIs de outros sistemas. Comunicao com hardware especfico, atravs de
bibliotecas que se comunicam com as portas seriais ou paralelas tambm faro um uso
extenso de ponteiros. Na indstria, softwares que controlam mquinas e sensores,
termmetros e aplicaes em tempo real tambm fazem um uso extenso de ponteiros. Alm
disso, se voc programa bibliotecas e APIs para outros programadores deve ter
conhecimento sobre ponteiros e alocao de memria.
Longe de ser um tratado detalhado sobre o assunto, demos apenas uma pincelada num
assunto to amplo e que no vai ficar desatualizado enquanto os nveis e camadas mais
baixos de nossos sistemas forem escritos em C/C++ e Assembly. Esperamos que esse artigo
seja til para seus ajustes finos e uso de bibliotecas de terceiros. At a prxima.