Do que trata o artigo

Neste artigo abordaremos ponteiros: sua sintaxe atual e mudanças ao longo das versões do Delphi, como e onde utilizá-los e sua evolução. Também abordaremos porque ainda hoje é importante conhecermos ponteiros, quais tipos usados são classificados como ponteiros e como funciona a alocação dinâmica de memória.


Para que serve

Para programar usando a API do Windows, desenvolver componentes, integrar com sistemas legados ou feitos em outras linguagens. Os ponteiros são a base para a alocação dinâmica de memória, e consequentemente os ambientes gerenciados trazem, em seu código fonte, um extenso uso de ponteiros.


Em que situação o tema é útil

Mesmo no Windows 7 a sua API ainda contém vários métodos cujos argumentos ou resultados são ponteiros. E neste nível dificilmente teremos programação orientada a objetos pura, ou um ambiente gerenciado. Estamos falando de um nível intermediário entre a sua aplicação e o sistema operacional. Sempre que tivermos um método em uma DLL escrita em C ou C++, o uso de ponteiros se fará necessário para que possamos passar variáveis por referência.

Resumo do DevMan

Ponteiros são endereços da memória e podem ser armazenados em variáveis. Não são valores, mas apontam para uma área da memória onde há um valor armazenado. Embora esse assunto possa parecer muito acadêmico, é ainda muito utilizado para se aproveitar ao máximo a API do Windows e até se desenvolver coisas novas, como novos componentes que fazem uso da API. Veremos neste artigo o básico de ponteiros, como eles podem ser usados para se passar variáveis por referência e qual sua relação com vetores.

Mesmo nas linguagens de “alto nível” como Delphi, ponteiros são o assunto de nível mais baixo que podemos tratar. Isso porque estaremos lidando diretamente com endereços da memória. Há quem defenda que ponteiros são coisa do passado e que não devem mais ser usados. Mas isso é um engano. Uma rápida olhada no código–fonte da VCL e da RTL pode revelar o tamanho uso que as próprias bibliotecas do Delphi fazem.

E mesmo em ambientes gerenciados como Python, Java, Ruby e .NET, embora não usemos ponteiros diretamente ou explicitamente, a máquina virtual, geralmente escrita em C/C++, faz um extenso uso de ponteiros. No próprio Delphi, todo objeto, cuja memória é alocada dinamicamente, é um ponteiro, como veremos mais adiante.

Para armazenar valores nós declaramos variáveis. Declarar uma variável significa reservar um espaço na memória onde poderemos armazenar um valor qualquer. Agora imagine que cada área da memória contém um endereço numérico. Ponteiros trabalham diretamente com os próprios endereços. Ou seja, você pode acessar um valor através de seu endereço na memória em vez de utilizar uma variável.

No Delphi utilizamos o operador ^ (circunflexo) para declarar uma variável do tipo ponteiro. Usamos também o operador @ precedendo variáveis “normais” para obter o endereço delas. O inverso de @ seria usar ^ depois de uma variável-ponteiro, para que possamos acessar o seu valor. Um ponteiro recém declarado que não aponta para nenhum lugar tem o valor nil.

Antes de iniciarmos a nossa aplicação, atente para a Figura 1 que exibe um exemplo do funcionamento de um ponteiro.

Figura 1. Um ponteiro apontando para um determinado endereço na memória, que contém o valor 2

Vamos desenvolver uma aplicação Delphi e ver na prática como tudo isso funciona (no Delphi crie um projeto Win32). Criaremos um botão para cada exemplo, nomeados como btEx<yy> onde yy será o número do nosso exemplo. Os captions de cada botão serão análogos, como “Ex 01”, “Ex 02” e assim por diante. Modifique a propriedade Name do formulário 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 endereço de memória apontando para um local onde será armazenado um valor inteiro. Para ver como funciona o mecanismo de ponteiros iremos declarar duas variáveis do tipo integer e obter o endereço de cada uma, e posteriormente o valor.

Não podemos esquecer de inicializar o nosso ponteiro com nil ou já com um endereço válido, caso contrário ele virá com um endereço randômico de memória, provavelmente apontando para algum “lixo” ou até mesmo para a área de instruções. Esquecer de inicializar ponteiros pode causar acessos indevidos a áreas protegidas de memória e até a corrupção de dados e instruções. Adicione ao formulário o primeiro botão e digite o código da Listagem 1.

Listagem 1. Declaração de ponteiros


  procedure TfrmPonteiros.btEx01Click(Sender: TObject);
  var
    variavel1: integer;
    variavel2: integer;
    ponteiro: ^integer;
  begin
    //Atribuindo um valor às variáveis
    variavel1 := 123;
    variavel2 := 246;
    //inicializando o ponteiro com nil
    ponteiro := nil;
    if ponteiro = nil then
      ShowMessage('O ponteiro ainda não aponta para lugar nenhum');
    ShowMessage('O endereço de memória da variável 1 é: ' + 
      inttostr(integer(@variavel1)) );
    ShowMessage('O endereço de memória da variável 2 é: ' + 
      inttostr(integer(@variavel2)) );  ponteiro := @variavel1;  ShowMessage('O endereço 
      atual do ponteiro é: ' + inttostr(integer(ponteiro)) );
    ShowMessage('O valor que ele aponta é: ' + inttostr(integer(ponteiro^)) );  
    ponteiro := @variavel2;
    ShowMessage('O endereço atual do ponteiro é: ' + 
      inttostr(integer(ponteiro)) );
    ShowMessage('O valor que ele aponta é: ' + inttostr(integer(ponteiro^)) );
    //mostrando os tamanhos
    ShowMessage('O tamanho da variavel 1 é: ' + inttostr(sizeof(variavel1)) );
    ShowMessage('O tamanho da variavel 2 é: ' + inttostr(sizeof(variavel2)) );
    ShowMessage('O tamanho do ponteiro é: ' + inttostr(sizeof(ponteiro)) );
  end; 

Os ponteiros que estamos usando aqui são “tipados”, ou seja, são ponteiros para um tipo definido, no caso integer. As três últimas linhas da listagem mostram o tamanho das variáveis, em bytes. As variáveis 1 e 2 são do tipo integer, por isso possuem 4 bytes. O ponteiro também possui 4 bytes, porém, todo ponteiro é, no fundo, um integer, e na arquitetura de 32 bits um ponteiro também tem 32 bits, ou seja, 4 bytes. Experimente trocar o tipo da variável de integer para byte e verá que enquanto as variáveis 1 e 2 tiverem 1 byte, o ponteiro continuará tendo 4 bytes.

Na Listagem 2 faremos um código similar, mas desta vez mudaremos o valor da variável e verificaremos qual será o valor para o qual o ponteiro aponta. Novamente insira um botão no formulário para a inclusão deste novo código.

Listagem 2. Operações com os valores apontados


  procedure TfrmPonteiros.btEx02Click(Sender: TObject);
  var
    variavel: integer;
    ponteiro: ^integer;
  begin  
    variavel := 50;
    ShowMessage('Valor da variável: ' + IntToStr(variavel));
    //atribuindo ao ponteiro o endereço da variável
    ponteiro := @variavel;
    ShowMessage('Valor apontado pelo ponteiro: ' + IntToStr(ponteiro^));
    variavel := variavel + 10;
    ShowMessage('Valor apontado pelo ponteiro após a soma: ' + 
      IntToStr(ponteiro^));
    ponteiro^ := ponteiro^ + 5;
    ShowMessage('Valor atual da variável: ' + IntToStr(variavel));
  end; 

Na Listagem 2 podemos ver que tanto faz referenciar um local na memória através de uma variável ou de um ponteiro, ambos apontarão para o mesmo local, e caso se mude esse valor, o valor atual será visível em quaisquer ponteiros que apontem para ele. Podemos ter vários ponteiros “apontando” ou referenciando um mesmo lugar na memória.

...

Quer ler esse conteúdo completo? Tenha acesso completo