Ponteiros Professor Sérgio Furgeri Revisão Ponteiros Variáveis dinâmicas Comparação entre variáveis estáticas e variáveis dinâmicas. ---------------------------------------------------------Até o presente momento, lidamos com variáveis que tiveram de ser criadas antes de se executar um programa. São variáveis que existem o tempo todo, ou seja, são variáveis estáticas. Portanto, a alocação de memória para esse tipo de variável é feita antes da execução do programa. A grande desvantagem desse tipo de variável é o fato de que uma vez criada, o espaço de memória que ela ocupa não pode mais ser alterado. As variáveis dinâmicas podem ser criadas e ou destruídas durante a execução de um programa, e esta, é a grande vantagem delas sobre as estáticas. As variáveis dinâmicas podem ser obtidas através de um tipo prédefinido em Pascal, chamado Pointer. O pointer ou apontador, como o próprio nome diz, aponta para um local de memória onde está armazenada uma variável. O procedimento para se declarar uma variável do tipo pointer é simples: Var p : ^Integer; Essa declaração apenas define uma variável do tipo pointer, que irá apontar o endereço de uma variável dinâmica do tipo Integer. Por enquanto a variável tipo Pointer não está apontando para nenhum lugar (NIL). É o mesmo que atribuir NIL a variável (p:=NIL;) Sempre que criamos uma variável do tipo pointer, ela tem o valor inicial NIL. Criação de variáveis dinâmicas. Para criar uma variável dinâmica é usada a procedure New, interna do Pascal. Sua sintaxe é: New(p); Isto faz com que seja alocado um espaço de memória, suficiente para armazenar uma variável do tipo associado a p, no caso integer. Esse espaço de memória fica num local especial chamado HEAP. No caso do IBM/PC, o HEAP é toda a memória não utilizada pelo sistema. Portanto, a declaração New(p) aloca um espaço de memória no HEAP, suficiente para armazenar uma variável do tipo Integer e retorna o endereço inicial desta região de memória para a variável p. Lembre-se que p é do tipo pointer. Para acessar o conteúdo dessa variável é usada a seguinte simbologia: p^ Para atribuir um novo endereço a variável: p:=add(i); Nesse caso p recebe o endereço de i. Exemplo do uso de Ponteiros: Program Exemplo; Uses CRT; Type Ponteiro = ^Integer; Var p : Ponteiro; i : Integer; {p é uma variável do tipo pointer que aponta para variáveis dinâmicas do tipo integer} Begin ClrScr; If p = NIL Then Writeln('A variável está sem endereço'); {como p acabou de ser criada, ela não está apontando para nenhum endereço, ou seja, seu valor inicial deve ser NIL. Para descobrirmos se isso é verdadeiro, basta compará-la com NIL} New(p); {acabamos de criar uma variável dinâmica do tipo Integer, e seu endereço foi colocado no pointer p } Página 1 Ponteiros Professor Sérgio Furgeri p^:=100; {estamos atribuindo o valor 100 à variável dinâmica recém criada } Writeln(p^); i:=200; p^:=i; Writeln(p^); {será escrito 200} { A funçäo addr(var) retorna o endereço da variável var } p:=addr(i); { o pointer contém agora o endereço da variável i } p^:=1000; Writeln(i); {Será escrito 1000} End. Exemplo 2: Criar um programa em Pascal que permite acessar o conteúdo da memória tanto pelo nome da variável, quanto pelo seu endereço. Solução: program pram; uses crt; var NUM1, NUM2 : byte; LETRA1, LETRA2, LETRA3 : char; PNUM1, PNUM2 : ^byte; PLETRA1, PLETRA2, PLETRA3 : ^char; begin clrscr; {acessando a memória pelos nomes} PNUM1 := addr (NUM1); { ou PNUM1:=@NUM1 } PNUM2 := addr (NUM2); PLETRA1 := addr (LETRA1); PLETRA2 := addr (LETRA2); PLETRA3 := addr (LETRA3); {atribuindo valores as variáveis} NUM1 := 1; NUM2 := 200; LETRA1 := 'A'; LETRA2 := 'B'; LETRA3 := 'C'; {acessando a RAM pelos nomes e por seus endereços} writeln (NUM1, '=', PNUM1^); writeln (NUM2, '=', PNUM2^); writeln (LETRA1, '=', PLETRA1^); writeln (LETRA2, '=', PLETRA2^); writeln (LETRA3, '=', PLETRA3^); Página 2 Ponteiros Professor Sérgio Furgeri {escrevendo os endereços onde os conteúdos estão armazenados} writeln (longInt(PNUM1)); writeln (longInt(PNUM2)); writeln (longInt(PLETRA1)); writeln (longInt(PLETRA2)); writeln (longInt(PLETRA3)); end. Questão 2. Como obter os endereços reais ocupados pelas variáveis ? R: Como vimos, basta utilizarmos a função addr. Outra maneira é utilizar o símbolo @ (at) ao lado esquerdo do nome da variável. Ex1: Para armazenarmos o endereço num ponteiro: P := addr(x); ou P:=@x; Ex2: Para exibirmos o endereço da variável x na tecla do micro, basta utilizar o comando write (LongInt(P)); Esse comando permite constatar que cada tipo de variável ocupa uma quantidade diferente de memória. Obs: Se usássemos simplesmente write (addr (x)), o Pascal daria um erro, pois ele não nos permite escrever um endereço na tela diretamente. Para contornarmos esse problema, convertemos o endereço para um tipo inteiro. Questão 3.Qual a diferença entre P e P^ ? R: O conteúdo de P é um endereço de memória, enquanto P^ é o conteúdo do endereço apontado por P. Questão 4. Um ponteiro aponta sempre para o endereço de uma variável? R: Não! Podemos ter ponteiros que simplesmente apontam para uma área de memória sem nome (ou seja, uma área de memória RAM em relação a qual nenhuma variável está associada). Ex: O comando new(P) aloca um espaço de memória RAM e atribuiu o seu endereço à variável P. Desse modo, a única maneira de acessarmos essa memória é pelo seu endereço, pois nenhuma variável foi utilizada para nomear a memória. Questão 5. Quando utilizamos o comando new(P), sabemos que uma área da memória é alocada e seu endereço é atribuído a P. Entretanto, qual o tamanho e o formato da área alocada? R: O tamanho e o formato da área alocada depende do tipo base do ponteiro. Abaixo são mostrados vários exemplos: Ex1: var P1: ^char; P2: ^real; begin new (P1); {cria uma área no formato do tipo char} new (P2); {cria uma área no formato do tipo real} P1^ := ‘A’; Página 3 Ponteiros Professor Sérgio Furgeri P2^ := ’32.1’; end. Ex2: type Tfunc = record nome: string; idade: byte; end; var P: ^Tfunc; begin new (P); {alocar uma área de memória no formato Tfunc} P^.nome:= ‘João Beterraba’; P^.idade:= 20; writeln (‘Eu sou o ‘, P^.nome,); writeln (‘Tenho’, P^.idade, ‘ anos’); end. Conclusão: Para todos os efeitos, P^ pode ser entendida como sendo uma variável do mesmo tipo que o tipo da base de P. Isso é sempre verdade, qualquer que seja o tipo base de P. Questão 6. Como declaramos um ponteiro? var Sintaxe: nome_ponteiro : ^tipo_base; Ex: var P1 : ^char; {ponterio cujo tipo base é char} P2 : ^real; {ponteiro cujo tipo base é real} Questão 7. Por que devemos especificar um tipo base para um ponteiro? R: É por meio do tipo base que o Pascal saberá quais são os tipos de valores que podem ser armazenados nos endereços referenciados pelos ponteiros. Por exemplo, se o tipo base de um ponteiro é o tipo char então somente valores do tipo char poderão ser armazenados no endereço apontado pelo ponteiro. Ex: {ERRADO} var P : ^char; begin New (P); P^ := 10; {o Pascal apontará um erro, pois estamos atribuindo um número a uma área de end. memória que somente pode armazenar char} Página 4 Ponteiros Professor Sérgio Furgeri {CORRETO} var P : ^char; begin New (P); P^ := ‘A’; {isso está correto, pois estamos armazenando o caracter ‘A’} end. Estruturas de dados com ponteiros Exemplo 1: Um programa para verificar a memória disponível no sistema. (pág.105) program MemDisp2; pagina 165} uses crt; {modificado do livro turbo pascal 6 - completo e total, var p:^string; begin clrscr; while true do begin writeln(memAvail); new(p); if memAvail<500 then begin Writeln('Nao ha memoria suficiente'); break; end; end; readkey; end. Exemplo 2: Um programa que mostra o funcionamento do Dispose, seg() e Ofs(). program Dispose1; {retirado do livro turbo pascal 6 - completo e total, pagina 165} uses crt; var p:^string; begin clrscr; new(p); p^:='1234567890'; writeln(p^); writeln(seg(p)); { Segmento de mem¢ria } writeln(ofs(p)); { Posicao de mem¢ria } Writeln(MemAvail); { mostra a memoria disponivel } dispose(p); { Libera a posicao de mem¢ria } Writeln(MemAvail); { mostra a memoria disponivel } writeln(p^); { coloca uma marca no conteudo de p } Página 5 Ponteiros Professor Sérgio Furgeri readkey; end. Liberando espaço em memória Tudo o que foi alocado procedure dispose. Ex: com a procedure new, deverá ser desalocado com var A: ^Integer; begin new(A); { Cria uma área na memória para alocar um Integer A^:=10; { Coloca o valor 10 nessa nova área da memória dispose(A); { Desaloca o espaço reservado pela procedure New end. a } } } Página 6