Ponteiros no Pascal / Delphi - Fundamentos

Continuando o post anterior, damos seqüência mostrando o artigo do HowStuffWorks que fala sobre os fundamentos da utilização de ponteiros em C e eu em paralelo falarei da sua utilização no Pascal / Delphi.

Antes de começar peço atenção, pois no mesmo texto eu misturei explicações de C e Pascal, quando isso acontece eu usei a cor azul para identificar o código em C e a cor vermelha para identificar código em Pascal.

Fundamentos sobre ponteiros

Para compreender os ponteiros, convém compará-los às variáveis normais.

Uma “variável normal” é um local na memória que pode conter um valor. Por exemplo, quando você declara uma variável i como um inteiro, 4 bytes de memória são separados para isso. Em seu programa, você se refere àquele local na memória pelo nome i. No nível da máquina, aquele local tem um endereço de memória. Os 4 bytes naquele endereço são conhecidos pelo programador como i, e os 4 bytes podem conter valores inteiros.

Um ponteiro é diferente. Um ponteiro é uma variável que aponta para outra variável. Isto significa que um ponteiro mantém o endereço de memória de outra variável. Em outras palavras, o ponteiro não contém um valor no sentido tradicional, mas sim o endereço de outra variável. Um ponteiro “aponta para” esta outra variável mantendo uma cópia de seu endereço.

Como um ponteiro contém um endereço, e não um valor, terá duas partes. O ponteiro contém um endereço e o endereço aponta para um valor. Há o ponteiro e o valor para o qual ele aponta. Este fato pode ser um tanto confuso, até você se familiarizar com ele. Superada a etapa da familiarização, ele se torna extremamente eficaz.

A codificação exemplificada a seguir mostra um típico ponteiro:

#include <stdio.h>  
int main()
{
    int i,j;
    int *p;
 /* um ponteiro para um inteiro */
    p = &i;
    *p=5;
    j=i;
    printf("%d %d %d\n", i, j, *p);
    return 0;
}  
 

Fazendo um paralelo com o Pascal / Delphi temos:

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  i, j: Integer;
  p: ^Integer;
begin
  { um ponteiro para um inteiro }
  p := @i;
  p^ := 5;
  j := i;
  WriteLn(Format(‘%d %d %d’, [i, j, p^]));
  ReadLn;
end.
 

A primeira declaração neste programa mostra duas variáveis inteiras normais, nomeadas i e j . A linha int *p ou p: ^Integer declara um ponteiro denominado p. Esta linha pede que o compilador declare uma variável p que seja um ponteiro para um inteiro. O * indica que foi declarado um ponteiro em vez de uma variável normal, no caso do Pascal temos o circunfléxo ^ antes do tipo para indicar que é um tipo ponteiro. Você pode criar um ponteiro para qualquer coisa: um flutuante, uma estrutura, um caractere e assim por diante. Basta usar um * para indicar que deseja um ponteiro em vez de uma variável normal, e no Pascal basta acrescentar um ^ antes do tipo para indicar que um ponteiro para aquele tipo.

Saliento que há uma diferença semantica na tipificação de ponteiros em C e em Pascal. No C declaramos que a variável é um ponteiro, no Pascal é o “tipo” que é um ponteiro. Reforçando, no C, colocamos o * antes do nome da variável e no Pascal colocamos o ^ antes do tipo. Esse tipo de construção do Pascal permite criar “tipos ponteiros”, inclusive este é o padrão recomendado, exemplificando, ao invés de usarmos p: ^Integer, devemos primeiro definir um tipo novo e depois declar a variável/ponteiro com este tipo novo:

type
  PInteger = ^Integer;
var
  i, j: Integer;
  p: PInteger;
 

Inclusive é comum em Pascal usar a letra P no início do nome do tipo ponteiro.

A linha p = &i; trará algo totalmente novo para você. Em linguagem C, & é denominado operador de endereço. A expressão &i significa: “O endereço de memória da variável i“. Assim, a expressão p = &i; significa: “Atribuir para p o endereço de i”. Uma vez executada esta instrução, p “aponta para” i.

No Pascal / Delphi, o operador @ tem a mesma funcionalidade do operador & em C. Assim sendo eu posso repetir o paragrafo anterior apenas trocando para Pascal:

A linha p := @i; trará algo totalmente novo para você. Em linguagem Pascal, @ é denominado operador de endereço. A expressão @i significa: “O endereço de memória da variável i“. Assim, a expressão p := @i; significa: “Atribuir para p o endereço de i”. Uma vez executada esta instrução, p “aponta para” i.

Antes de prosseguir, lembre-se que p contém um endereço aleatório e desconhecido e que sua utilização causará uma falha de segmentação ou erro de programa similar.

Apenas complementando, se p não for iniciado ele poderá conter um endereço qualquer de memória. Este é um dos riscos de se usar um ponteiro.

Um bom método para visualizar o que está acontecendo é fazer um desenho. Depois que i, j e p são declarados, as coisas parecerão assim:

 c-pointer1

Neste desenho, as três variáveis i, j e p foram declaradas, mas nenhuma delas foi inicializada. As duas variáveis inteiras foram desenhadas como caixas contendo os pontos de interrogação (elas poderiam conter qualquer valor nesta altura da execução do programa). O ponteiro é desenhado como um círculo para distingüi-lo de uma variável normal que contém um valor e as setas aleatórias indicam que ele pode estar apontando para qualquer lugar neste momento.

Depois da linha p = &I; ou p := @i;, p é inicializado e aponta para i, desta forma:

c-pointer2

Assim que p aponta para i, o local de memória i tem 2 nomes. Ele ainda é conhecido por i , mas agora também é conhecido como *p ou p^. É assim que a linguagem C (ou Pacal) se comunica com as duas partes de uma variável de ponteiro: p é o local que mantém o endereço, enquanto *p ou p^ é o local apontado por aquele endereço. Logo, *p=5 ou p^ := 5 significa que o local apontado por p deve ser definido como 5, assim:

c-pointer3

Visto que o local *p ou p^ também é i, i também assume o valor 5. Conseqüentemente, j=i;  ou j := i; define j como 5 e a instrução printf ou Format produz 5 5 5.

A principal característica de um ponteiro é sua dupla natureza. O próprio ponteiro contém um endereço. O ponteiro também aponta para um valor de um tipo específico: o valor no endereço do ponteiro. O próprio ponteiro, neste caso, é p. O valor apontado é *p ou p^.

Acho que com este post damos chute inicial para o entendimento dos ponteiros.

6 Respostas para “Ponteiros no Pascal / Delphi - Fundamentos”


  1. 1 Francisca

    Esse artigo me salvou… parabéns muito bom.

  2. 2 Tiago

    Eu não consigo definir um ponteiro em uma procedure, ou como eu poderia fazer?

    Exemplo do quero fazer:

    Procedure pCriaForm( Form : ^TForm ; FormClass : TFormClass ) ;
    Begin

    If ( Form^ = Nil ) Then
    Form^ := FormClass.Create( Application ) ;
    Form^.Show;

    End;

    Para chamar assim: pCriaForm( Form1 ; TForm1 );

    Obrigado

  3. 3 Alecão

    @Tiago, a construção esta certa, apenas precisa de alguns ajustes:

    type
    PForm: ^TForm;

    Procedure pCriaForm( Form : PForm ; FormClass : TFormClass ) ;
    Begin

    If ( Form^ = Nil ) Then
    Form^ := FormClass.Create( Application ) ;
    Form^.Show;

    End;

    e chama assim: pCriaForm( @Form1 , TForm1 );

    Mas o mais simples nesse caso é não usar ponteiro:

    Procedure pCriaForm(var Form : TForm ; FormClass : TFormClass ) ;
    Begin

    If ( Form = Nil ) Then
    Form := FormClass.Create( Application ) ;
    Form.Show;

    End;

    e chama assim: pCriaForm( Form1 , TForm1 );

    o “var” indica que aquele parâmetro é por referência e não por valor, assim a alteração da variável dentro da procedure reflete na variável passada. Como disse nos artigos, apesar do Pascal ter manipulação de ponteiros, as vezes estes não são necessários.

  4. 4 Tiago

    Caro Alecão,

    Utilizando da segunda forma como você me disse, a variável Form do pCriaForm não seria a mesma variável que a Form1.
    Nesse caso ela sempre comparará como Nil e criará um novo Form.
    Faça o teste sem mandar destruir o Form ao fechar que você verá.

    Para isso que estive procurando um bom exemplo de ponteiro como o seu.

    Muito Obrigado!

  5. 5 Alecão

    @Tiago, estou vendo que seu problema é outro. Se liberarmos o Form com Form1.Free por exemplo, apenas liberamos o objeto mas a referência para Form1 continua apontando para um endereçamento que não existe mais, ou seja, ele é diferente de “nil”.

    Este problema irá ocorrer com uso de ponteiro também.

    Por isso, quando liberar o form, temos também que atribuir nil a referência, assim:

    Form1.Free;
    Form1 := nil;

    ou simplesmente usar uma função disponível na unit SysUtils:

    FreeAndNil(Form1);

  6. 6 Tiago

    Opa, obrigado Alecão!

    Adotei!
    Abraços!

  1. 1 Ponteiros no Pascal / Delphi - Entenda os endereços de memória de DevelopeZ

Deixe uma Resposta