segunda-feira, 12 de março de 2007

Ponteiros

O armazenamento de qualquer valor no computador é feito na memória. Quando algo esta sendo processado, o armazenamento dos valores referentes a este processo é feito na memória principal. Como o computador acessa esses valores? Cada lugar da memória possui um endereço, ao acessarmos um determinado endereço de memória obtemos o valor armazenado naquele momento, naquele endereço.

Quando um programador está escrevendo um programa que calcule a soma de dois valores, ele vai precisar armazenar temporariamente três valores: Determinado número "X", determinado número "Y" e o Resultado da soma.
Para fazer isso efetivamente, ele declara variáveis. O que ocorre nos bastidores quando isso é feito? O compilador reserva um espaço na memória (ou seja, um endereço) para o armazenamento de um determinado valor (o espaço vai variar de acordo com o tipo definido para a variável quando ela foi declarada). O Programador, vê a variável “Num1”, do tipo numérico inteiro que ele criou, enquanto que o compilador “entende” aquilo como um endereço da memória aonde será armazenado o valor atribuído aquela variável (esse endereço é representado por um número hexadecimal ). Posso concluir então, que é possível acessar um determinado valor armazenado direto pelo endereço dele? Certamente sim. Isso que acabei de explicar, pode não fazer nenhuma diferença para vc agora. Contudo, em determinadas situações o entendimento desse mecanismo pode fazer a diferença entre vc estar no céu ou no inferno no mundo da programação.

Existe um tipo de dado chamado de ponteiro. Ele serve para armazenar a referência de um endereço de memória de uma variável qualquer. Como assim? Ponteiro, aponta para algum lugar. Aponta para um endereço, um local na memória principal do computador. A idéia seria, na prática, o ponteiro te leva imediatamente ao endereço para o qual ele aponta.
Num primeiro momento isso pode parecer um complicação desnecessária, na medida em que agora precisamos nos preocupar não só com o valor a ser armazenado, mas também com o endereço aonde isso será feito. Mas antes de tirar conclusões precipitadas vamos nos aprofundar um pouco mais no assunto, testar alguns exemplos para experimentarmos na prática o quanto a utilização de ponteiros pode tornar seus programas muito mais poderosos.


Embasamento Teórico

Variáveis Estáticas: trata-se das váriaveis cujo compilador reserva espaço para elas estaticamente, no momento da linkedição. Espaço esse que por sua vez será alocado na memória stack. Por exemplo: Uma variável do tipo "Integer" (a qual chamaremos hipoteticamente de ZnInteger) que vôce declarar no seu programa, durante a excução do mesmo, obviamente, em algum momento será alocado um espaço de 32 bits para aquela variável (ZnInteger). O Compilador aloca estaticamente espaço para esse tipo de variável e o próprio se preocupa em desalocar este espaço. Exemplo:

Em Pascal -

Num1: Integer; { A é uma variável do tipo Integer, e ocupará 2 bytes na memória enquanto o programa estiver em execução }

Em C ou Java
      
int Num1;

Alocação Dinâmica: Em run time (em tempo de excução), dinamicamente, alocamos espaço de memória. Quando isso é feito somos responsáveis por desalocar essa memória. Exemplo:
Em Pascal
      procedure GetMem(var P: Pointer; Size: Integer); //(Para alocar)  

procedure FreeMem(var P: Pointer[; Size: Integer]); //(Para desalocar)

Ponteiros: Tipos de variáveis que apontam para um endereço de memória específico. Os Ponteiros podem apontar para:
  
1) Valores, áreas com Dados (Exemplo: uma variável. Ou melhor o conteúdo de uma
variável.
2) Uma rotina (Procedure ou Function);
3) Endereço nulo.



Sobre as variáveis:
- tipadas - o ponteiro conhece qual o tipo de dado para o qual está apontando.
- não tipadas - o ponteiro não conhece para que tipo de dado está apontando. Nestes casos seremos obrigados a fazer um cast, um Typecast a fim de definirmos para o compilador como deve ser tratado o condeúdo do endereço que estamos acessando.Falaremos sobre Typecast mais adiante.

Para inicializar um ponteiro com o endereço de uma variável usamos o operador @(arroba). Ex.:

AuxZn := @ZnValue; { Aux recebe o endereço da variável Value }


Para inicializar um ponteiro com um novo endereço de memória, veja trecho ilustrando abaxio:

var
ZnAux: ^Integer;
begin
(* ZnAux - aponta para um endereço de memória reservado para o tipo Integer*)
new(ZnAux);
end;


O que está acontecendo aqui? Foi reservado um endereço de memória e “ZnAux” está apontando para essa área.

Neste último caso o programador será responsável pela alocação, bem como pela desalocação. No primeiro caso isto não é necessário (e sequer deverá ser feito), pois o endereço de memória pertence à variável estática “ZnValue”.

Apontando Rotinas: Os ponteiros podem armazenar o endereço de mémória de um "procedure" ou "function".

Apontando para "null": A palavra reservada "nil" significa valor nulo. Trata-se de declarar um ponteiro que não aponte para nenhum endereço de memória. Exe:
ZnAuxPT := nil; 

O ponteiro ZnAuxPT não está apontando para dado algum. Logo, podemos testar quando um ponteiro aponta para algum valor:

if ZnAuxPT = nil then
Showmessage('Não há Valor, não ha endereço. Ou melhor, o endereçoé nulo!')
else
Showmessage ('ZnAuxPT o Endereço não é nulo');


Declarando ponteiros

Em pascal:
Para declarar um ponteiro, na seção "var", definimos o identificador, seguido de dois pontos (":"), o tipo para o qual ele apontará, precedido pelo operador "^" (circunflexo).



var
ZnPtNum: ^Integer; { ZnPtNum aponta para um Integer }
ZnPtChar: ^string; { ZnPtChar aponta para uma string}
begin


Para declarar um ponteiro não tipado:

var
ZnPtVar: Pointer;
{ ZnPtVar é uma variável ponteiro que apontar para qualquer tipo de dado }


Declarando ponteiro para uma procedure ou função:

var
ZnPtProc: procedure(ZnX: String);
ZnResultado: Integer;
procedure ZnProcedureStr(ZnAlfa: String);
begin
ShowMessage('ZnProcedureStr recebeu ', x);
end;

function ZnFunctionSoma(ZnNun1, ZnNun2: Integer);
begin
ShowMessage(Format('ZnFunctionSoma somou %d com %d. O Reseultado é: %d ',
[ZnNun1, ZnNun2, (ZnNun1 + ZnNun2)]));
end;

begin
ZnPtProc := @ZnFunctionSoma;
ZnResultado := ZnPtProc(2, 4);
if ZnResultado = 6 then
begin
ZnPtProc := @ZnProcedureStr;
(* Exibirá: 'Rotina 2 recebeu Estação Zn: estacaozn.blogspot.com'*)
ZnPtProc("Estação Zn: estacaozn.blogspot.com");
end;
end;




Endereço X Valor armazenado



É importante destacar que os ponteiros nos dão acesso a duas informações:

uma é com o endereço para o qual ela aponta


A outra é o valorarmazenado no respectivo endereço.




var
ZnPtInt: ^Integer;
ZnInt: Integer;
begin
{ A linha abaixo faz com que “ZnPtInt” aponte para o endereço de ZnInt, é lida
assim: "ZnPtIntrecebe o endereço de ZnInt" }
ZnPtInt := @ZnInt;
{ primeira forma - estamos trabalhando o endereço para o qual ZnPtInt está
apontando}

{ A linha abaixo guarda o valor 5 na área de memória apontada porZnPtInt, é lida
assim: "O endereço apontado por A recebe 5 }
ZnPtInt^ := 5;
{ segunda forma - estamos trabalhando com a informação apontada por ZnPtInt}

ShowMessage(Format('O valor é: %d.', [ZnInt]));
end.


Na linha 7, definimos que ZnPtInt deverá apontar para ZnInt, em seguida colocamos o valor 5 no endereço apontado por ZnPtInt
(agora ZnPtInt aponta para ZnInt), em seguida exibimos o valorarmazenado por ZnInt.


Typecast


Sobre Typecast, veja ....
Exemplo Dummy:

var
ZnPtVar: Pointer;
ZnNum: Integer;
begin
ZnPtVar := @ZnNum;
ZnNum := (10 * 200);
ShowMessage(Format('Estação zn! O Valor é: %d', Integer(ZnPtVar^));


Usamos o circunflexo após a variável ponteiro para poder acessar o valor apontado.

Desalocando o Ponteiro


Ao usarmos a procedure new alocamos memória para um ponteiro. O mesmo deverá ser desalocado. Para isso evocamos a procedure “dispose”. Exemplo:

procedure ZnDesaloca;
var
ZnPtAux: ^Integer;
begin
new(ZnPtAux); { Aloca memória para um Integer }
{ Atribui valor a ZnPtAux. O endereço de memória passa a armazenar esse valor}
ZnPtAux^:= 210;
dispose(Aux); { Desaloca o espaço reservado pela procedure New }
end.

Um comentário:

  1. louvado seja a estaçãozn, que explica com uma linguagem descomplicada toda aquela parte dificil de entender nas linguagens de programação
    Cara... sou programador a 5 anos e nunca vi nada tão simples e claro assim. valeu, muito bom.

    ResponderExcluir