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

Dado o fundamento, agora iremos explanar como funciona o armazenamento destas informações na memória com um copy/paste deste artigo do HowStuffWorks.

Todos os computadores têm memória, também conhecida por RAM (memória de acesso aleatório). Por exemplo, seu computador pode ter 16, 32 ou 64 megabytes de memória RAM instalados (hoje em dia são gigabytes). A memória RAM mantém os programas que atualmente estão em execução no computador, junto aos dados que estão sendo manipulados (suas variáveis e estruturas de dados). A memória pode ser vista como uma matriz de bytes. Nesta matriz, cada local de memória tem seu próprio endereço. O endereço do primeiro byte é 0, seguido por 1, 2, 3, e assim sucessivamente. O endereço de memória atua como os índices de uma matriz normal. O computador pode acessar qualquer endereço na memória, a qualquer momento (por isso o nome “memória de acesso aleatório”). Ele também pode agrupar os bytes necessários para formar matrizes, variáveis e estruturas maiores. Por exemplo, uma variável de ponto flutuante consome 4 bytes contínuos em memória. Você pode fazer a seguinte declaração global em um programa:

float f; /* C*/

var f: Single; { Pascal }

Esta instrução diz: “Declare um local denominado f que possa manter um valor de ponto flutuante”. Quando o programa é executado, o computador reserva espaço para a variável f em algum lugar na memória. Aquele local tem um endereço fixo no espaço de memória, assim:

c-pointer4

A variável f consome 4 bytes de RAM na memória.
Este local tem um endereço específico, neste caso 248,440.

Enquanto você pensa na variável f, o computador pensa em um endereço específico na memória (por exemplo, 248,440). Assim, ao criar uma instrução como esta:

f = 3.14;  /* C */

f := 3.14; { Pascal }

O compilador poderia traduzir isso como: “Carregar o valor 3.14 no local de memória 248,440″. O computador está sempre pensando na memória em termos de endereço e valores para tais endereços.

A propósito, existem vários efeitos colaterais interessantes na forma com a qual seu computador gerencia a memória. Por exemplo, suponhamos que você incluiu a seguinte codificação em um de seus programas:

int i, s[4], t[4], u=0;
for (i=0; i<=4; i++)
{
  s[i] = i;
  t[i] = i;
}
Printf("s:t\n");
for (i=0; i<=4; i++)
  printf("%d:%d\n", , s[i], t[i]);
printf("u = %d\n", u);

O paralelo em Pascal / Delphi, temos:

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  i: Integer;
  s, t: array [0..3] of Integer;
  u: Integer;
begin
  u := 0;
  for i := 0 to 4 do
  begin
    s[i] := i;
    t[i] := i;
  end;
  WriteLn(’s:t’);
  for i := 0 to 4 do
    WriteLn(Format(‘%d:%d’, [s[i], t[i]]));
  WriteLn(Format(‘u = %d’, [u]));
  ReadLn;
end.

O resultado que você vê no programa provavelmente será algo assim:

s:t
0:4
1:1
2:2
3:3
4:4
u = 4

Por que t[0] e u estão incorretos? Se olhar cuidadosamente o código, você verá que os loops for estão escrevendo um elemento além do final de cada matriz. Na memória, as matrizes são colocadas lado a lado, como mostrado abaixo:

c-pointer5

Logo, ao tentar escrever para s[4], que não existe, o sistema escreve para t[0], uma vez que t[0] é onde s[4] deveria estar. Ao escrever para t[4], na verdade você está escrevendo em u. No que se refere ao computador, s[4] é simplesmente um endereço e pode ser escrito. Como você pode ver, mesmo que o computador execute o programa, ele não está correto ou válido. O programa corrompe a matriz t no processo de execução. A execução da seguinte instrução pode resultar em conseqüências mais graves:

s[1000000] = 5; /* C */

s[1000000] := 5; { Pascal }

O local s[1000000] provavelmente está fora do espaço de memória de seu programa. Em outras palavras, você está escrevendo na memória que seu programa não possui. Em um sistema com espaços protegidos de memória (UNIX, Windows 98/NT), este tipo de instrução fará com que o sistema encerre a execução do programa. Em outros sistemas (Windows 3.1, Mac), todavia, o sistema não sabe o que você está fazendo. Você acaba danificando o código ou as variáveis em outro aplicativo. O efeito da violação pode variar de nenhum a uma tremenda pane do sistema. Na memória, i, s, t e u são todos colocados próximos uns aos outros em endereços específicos. Portanto, se você escrever além dos limites de uma variável, o computador fará o que foi solicitado, porém acabará corrompendo outro local de memória.

As linguagens C e C++ não executam qualquer tipo de verificação de extensão ao acessar um elemento na matriz. É essencial que você, como programador, preste atenção às extensões de matriz e respeite seus limites. A leitura ou escrita não-intencional fora dos limites da matriz quase sempre leva ao comportamento errôneo do programa.

O Pascal / Delphi por padrão também não executa qualquer tipo de verificação de extensão ao acessar um elemento na matriz. Mas isto pode ser modificado com a diretiva $R. Se no código anterior acrescentarmos {$R+} no início, ao acessar o elemento 4 o software lança a excessão ERangeError com a mensagem Range check error. Mas não é recomendo pois torna a execução mais lenta do código.

Como outro exemplo, tente o seguinte:

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

Em Pascal / Delphi temos:

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  PInteger = ^Integer;
var
  i: Integer;
  p: PInteger;
begin
  WriteLn(Format(‘%p %p’, [p, @i]));
  p := @i;
  WriteLn(Format(‘%p %p’, [p, @i]));
  ReadLn;
end.

Este código diz ao compilador para imprimir o endereço contido em p, junto com o endereço de i. A variável p começa com um valor estranho ou com 0. O endereço de i geralmente é um valor alto. Por exemplo, quando executei este código, recebi o seguinte resultado:

7FFDD000 0040A600
0040A600 0040A600

que significa que o endereço de i é 0040A600 (em hexadecimal). Após executar a instrução p = &i; ou p := @i;, p contém o endereço de i. Tente também:

#include <stdio.h>
void main()
{
  int *p;
  /* um ponteiro para um inteiro */
  printf("%d\n",*p);
}

Em Pascal / Delphi temos:

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  PInteger = ^Integer;
var
  p: PInteger;
begin
  WriteLn(Format(‘%d’, [p^]));
  ReadLn;
end.

Este código diz ao compilador para imprimir o valor para o qual p aponta. Porém, p ainda não foi inicializado. Ele contém o endereço 0 ou algum endereço aleatório. Na maioria dos casos, resulta em uma falha de segmentação (ou algum outro erro no tempo de execução), o que significa que você usou um ponteiro que aponta para uma área inválida da memória. Quase sempre, um ponteiro não inicializado ou um endereço de ponteiro inválido é a causa das falhas de segmentação.

Dito isso, agora podemos ver os ponteiros por uma ótica totalmente diferente. Veja este programa, por exemplo:

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

Em Pascal / Delphi temos:

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  PInteger = ^Integer;
var
  i: Integer;
  p: PInteger;
begin
  p := @i;
  p^ := 5;
  WriteLn(Format(‘%d %d’, [i, p^]));
  ReadLn;
end.

Eis o que está acontecendo:

c-pointer6

A variável i consome 4 bytes de memória. O ponteiro p também consome 4 bytes (na maioria das máquinas atuais, um ponteiro consome 4 bytes de memória; os endereços de memória tem 32 bits de extensão na maioria das CPUs, embora exista uma tendência de crescimento para o endereçamento de 64 bits). O local de i tem um endereço específico, neste caso 248,440. O ponteiro p contém aquele endereço, uma vez que você disse que p = &i; ou p := @i;. As variáveis *p ou p^ e i são, portanto, equivalentes.

O ponteiro p literalmente contém o endereço de i. Você pode dizer algo assim em um programa:

printf(”%d”, p);  /* C */

Format(’%p’, p); { Pascal }

o resultado é o endereço corrente da variável i.

9 Respostas para “Ponteiros no Pascal / Delphi - Entenda os endereços de memória”


  1. 1 Cleandro

    Pra uma pessoa que nunca viu Ponteiro, com esse conteúdo continuará ainda sem compreender.Seja mais claro, mais simples.

  2. 2 Silvia

    no caso de eu ter guardado um endereço na memória em um ponteiro e no decorrer do programa esta memória ficar livre, como faço pára verificar se meu ponteito ainda é válido? ou seja, se o endereço de memória q. eu tenho guardado nao esta livre?

  3. 3 Alecão

    @Silvia, depois que a memória ficar livre e o ponteiro ainda apontar para lá, não existe uma forma de verificar se o endereço para qual o ponteiro aponta ainda é válido (Infelizmente).

    É obrigação do desenvolvedor, criar processos que atribuam nulo (nil) nos ponteiros após liberarem os endereços de memória.

    Este é um problema clássico das linguagens como C, C++ e Pascal (Delphi). É a razão de muitos bugs e falhas de segurança que encontramos nos softwares, por descuido do programador de não controlar a referência.

    Nas linguagens modernas como Java e C# (.NET em geral), existe o conceito de “garbage collector” que tira da responsabilidade do programador a administração de liberação e referências.

  4. 4 CAIAN

    COMO FAÇO PARA LOCALIZAR O ENDEREÇO DA MEMORIA DE UMA DETERMINADA VARIAVEL EM UM OUTRO PROGRAMA?
    (EM DELPHI 7)

  5. 5 Alecão

    @CAIAN, entendi que esta perguntando: como uma aplicação acessa a memória de outra aplicação, falando de Win32 (é o caso dos executáveis gerados pelo Delphi 7) isso não é possível, já que cada aplicação (processo) é executada dentro de um nível de isolamento criado pelo sistema operacional (Windows). Caso queira fazer comunicação inter-processos, existem várias técnicas como objetos Mutex, DDL, Memória mapeada, etc…

  6. 6 CAIAN

    Por exemplo:
    Conhece o Tsearch ou o CheatEngine?
    Eles fazem exatamente o que eu quero..
    Procuram uma determinada ‘String’, ‘byte’, etc.. em outro processo…
    Porem gostaria de fazer isso em delphi =/

  7. 7 Alecão

    @CAIAN, não conhecia estes softwares. Mas curioso fui dar uma olhada. Inclusive o CheatEngine é Open Source e é feito em Delphi. Como eu te disse não é possível acessar diretamente um endereço de outro processo. Cheguei a esta conclusão dando uma olhada rápida no fonte:

    http://ce.colddot.nl/browser/Cheat%20Engine

    O CheatEngine faz uso a chamadas do Windows que controla o acesso a estes endereços de memória.

    Por exemplo, para “saber” as informações de memória de um determinado processo é usado a função VirtualQueryEx do Windows
    http://msdn.microsoft.com/en-us/library/aa366907(VS.85).aspx

    Eu particularmente fiquei curioso como você para saber o funcionamento em detalhe. Mas infelizmente meu tempo está apertado. Eu te aconselho a baixar o fonte do CheatEngine e estudar em detalhes.

  8. 8 CAIAN

    Obrigado!! Nao sabia que era OpenSource em Delphi!! =p
    Vou dar uma estudada nele!!
    Mas se por acaso voce conseguir entender ele antes, posta aqui para nos..
    Valeu Alecao!!

  9. 9 Andre SCOS

    Ola…

    Eu sei usar ponteiros e tal.

    Mais nao sei alguma ocasiao que use ponteiros, digo, nao sei 1 determinada tarefa que o uso de ponteiros seja imprescindivel saca?

    Queria que voce me falase alguma tarefa dessa em que o uso de ponteiros seja necessario.

    Abraços.

Deixe uma Resposta