Jump to content

Ponteiros


passarito

Recommended Posts

As variáveis ditas “normais”, têm uma característica comum, são entidades estáticas no que concerne à memória e são alojadas na stack memory.

Uma variável dinâmica é criada (na heap memory) e destruída durante a execução do programa.

A variável é referenciada por um apontador que aponta para um local da memória, isto é, enquanto uma variável pode ser alocada em qualquer local da memória que para nós é indiferente, a variável do tipo apontador vai apontar para um local específico da memória.

A título de exemplo, imaginem uma televisão na horizontal. Se colocar-mos em cima do ecrã um copo (a nossa variável) com uma bola dentro (conteúdo da variável) nós vimos sempre a bola. Podemos trocar a bola por outro objecto qualquer que caiba no copo. Se en vez de um copo colocarem um rolo de cozinha (ponteiro) e olhar-mos lá para dentro vimos a imagem que no momento estiver na zona do ecrã onde se encontra o rolo de cozinha, podendo variar. É claro que este exemplo é discutível. A ideia é que o ponteiro mostra-nos a informação da zona de memória para onde ele está a apontar.

Declaração

Type Nome_do_ponteiro=^Tipo_base

Todas as variáveis declaradas como sendo de um tipo dinâmico, vão apontar para dados do tipo base.

Exemplo como se podem declarar apontadores para inteiros

Type
  IntPtr=^Integer;
Var
  V1: IntPtr;

No caso de querem 3 variaveis do tipo IntPtr declarariam assim:

Var
V1, V2, V3: IntPtr;

Existem dois procedimentos standard em Pascal que permitem criar e destruir variáveis dinâmicas:

New(v1); - Aloca espaço na memória e cria uma variável dinâmica do tipo base declarado para v1.

Dispose(v1); Destrói a variável dinâmica apontada por v1 e liberta o espaço em memória para uso futuro.

Aproveitando o exemplo acima, vamos criar, atribuir um valor e destruir a variável

begin
  Write('Indique um número: ');
  New(v1);
  Readln(v1^);
  V1^ := v1^ + 5;
  Write(' O seu número adicionado de 5 unidades:  ',V1^);
  Dispose(v1);
end.

De realçar que os apontadores apenas levam ^ para se aceder à informação por ele apontada.

Poderão dizer pelo que até agora que os ponteiros são iguais às variáveis, só que com mais trabalho…. Efectivamente nesta primeira parte sim, mas o melhor vem a seguir.

Link to post
Share on other sites

LISTAS LIGADAS

Quem já não teve de declarar um array enorme e mesmo assim pensar que, numa situação particular qualquer pode não chegar? Pois é, com os ponteiros podemos criar listas ligadas sem nos preocupar-mos com o tamanho. É só alocar espaço para o próximo “indice”… haja memória, claro!

Para o fazermos, temos que recorrer a um tipo composto: os records

Neste record, colocamos um tipo especial… o apontador

Assim teríamos na declaração:

Type
  Ptr = ^Rec;
  Rec = record
    Marca: string[25];
    Tamanho: Integer;
    NextPtr: Ptr; {Serve para nos indicar o campo seguinte. Se for o último deve apontar para NIL}
  End;

Var
  Inicio, NovoReg , Aux1, RegAnterior: IntPtr;

Nota: Deve-se sempre colocar o último elemento da lista a apontar para NIL, caso contrário estamos sujeitos ao ponteiro ficar a apontar para um qualquer local da memória e termos surpresas desagradáveis… Querem fazer um vírus com os ponteiros? EASY!!! O S.O. carrega no inicio da memória  :)

Continuando, Quando se trata de construir listas ligadas ordenadas temos de ter em atenção 3 aspectos:

  - Descobrir onde pertence o registo;

  - Criar um nó (ajustar os ponteiros);

dev_geral:ptr1.jpg

Inserindo um Registo B

dev_geral:ptr2_1_.jpg

Então pegando na declaração anterior, vamos inserir um registo numa lista ligada ordenada pela marca.

Procedure le_insere_reg;
Begin
  New(NovoReg);
  Write('Marca do tubo: ');
  readln(NovoReg^.marca);
  Write('Tamanho: ');
  readln(NovoReg^.tamanho);
  Aux1:=Inicio;
  if (Incio=NIL) OR  (NovoReg^.marca<Aux1^.marca) then begin {1º elemento da lista se a lista está vazia, ou se marca menor que o primeiro elemento da lista}
    NovoReg^.NextPtr:=Inicio;
    Inicio:=NovoReg {NovoReg Não leva ^ porque queremos que Inicio aponte para a zona de memória onde está o NovoReg e não o seu conteúdo}
  end
  else begin   {Vamos percorrer a linha que tem inicio em Inicio e não queremos perder a referencia de memória de Inicio para o 1º registo da lista, então usa-se uma variável auxiliar atribuida anteriormente em Aux1:=Inicio;}
      While (NovoReg^.marca>=Aux1^.marca) AND (Aux1<>NIL) do begin
        RegAnterior:=Aux1;
        Aux1:=Aux1^.NextPtr;
      end;
      NovoReg^.NextPtr:=Aux1;
      RegAnterior^.NextPtr:=NovoReg;
  end;
end;

Foi este principio que me fez passar de um tempo de listagem de 3 Horas para 3minutos!

Ainda havia mais para dizer acerca das listas ligadas tal como listas duplamente ligadas, listas sem fim em que o ultimo está ligado ao primeiro, mas o essencial está aqui.

Edit: A Procedure le_insere_reg; foi corrigida em diversos locais!

Link to post
Share on other sites

Boa introdução à utilização de pointers :)

Uns reparos, apenas: não precisamos de declarar um novo tipo de dados para utilizar um pointer; podemos simplesmente declarar uma determinada variável como pointer para determinado tipo: p: ^integer;

Outra coisa: nem todas as variáveis locais (i.e. "não-dinâmicas") existem durante toda a execução. Qualquer variável local de uma função ou procedimento existe apenas durante a execução desse segmento de código. Aliás, a diferença entre as variáveis locais e as de alocação dinâmica é o local da memória onde são alocadas (stack e heap, respectivamente).

Quanto aos comandos new e dispose, quero apenas clarificar que existem comandos semelhantes (getmem e freemem) que oferecem um maior (e potencialmente mais perigoso) controlo sobre a memória alocada, permitindo assim maior eficiência. Mas isto já foge um pouco ao âmbito do post.

De resto, penso que faltou referir que um pointer tem um valor ele mesmo: o endereço da variável para a qual aponta.

Quanto às imagens, basta-te utilizar as tags [img ] com um URL da imagem que pretendes incluir no tutorial.

Bom tutorial, e obrigado por teres dado o salto para isto ;)

Link to post
Share on other sites

Boas,

Antes de mais obrigado pelos comentários.

Para começar a responder aos teus comentários, vou começar pelo mais simples, onde eu encrenco e sempre encrenquei!! Eh pá isso das imagens é muito bonito, já mais gente me disse isso, aqui e noutros foruns, mas a questão é: Em que URL? É aqui no forum? Algum site especifico? É que já vi referencias a imagens que depois vão dar a sites... umas vezes a imagem existe outras o não! É necessário ser registado?  ? São uma data de perguntas, se calhar óbvias para alguns, mas que para mim não  :crazy2: e se der tanto trabalho a postar uma imagem eu esqueço logo e bato texto!!! :uglystupid2:

Depois da minha burrice, a parte séria da questão:

podemos simplesmente declarar uma determinada variável como pointer para determinado tipo: p: ^integer;

Quando preparei este assunto dos ponteiros, (sim, porque já não tinha isto tudo assim tão fresqueinho!) também reparei que falavam nessa hipótese, mas para quê? Que vantagens adicionais trás essa hipótese? Como disse no artigo, os ponteiros só começam a fazer sentido com listas ligadas, àrvores, e pouco mais.

Poderão dizer pelo que até agora que os ponteiros são iguais às variáveis, só que com mais trabalho…. Efectivamente nesta primeira parte sim, mas o melhor vem a seguir.

Portanto, concordo contigo, mas esta foi a razão porque não o referenciei.

A mesma razão se aplica ao GetMem e FreeMem. Primeiro saber andar, depois saber correr! Se bem que estes têm uma importância maior do que o anterior.

Por fim, o mais complicado:

Outra coisa: nem todas as variáveis locais (i.e. "não-dinâmicas") existem durante toda a execução. Qualquer variável local de uma função ou procedimento existe apenas durante a execução desse segmento de código.

No inicio, é essa a informação que passam nas aulas de programação, mas mais tarde já ouvi dizer que não era bem assim. Já sei que tu, e se calhar mais alguém vai contra-argumentar, portanto vou já adiantar uma coisa.

Quando fiz este texto para os ponteiro, encontrei um programa para ver a memória (usada ou livre, já não me lembro). Então o que é que eu vou fazer? Além de postar esse programa aqui, vou fazer outro para verificar a memória utilizada antes e após o uso das das variáveis locais. Assim não temos de andar aqui com posts e mais posts  :argue: para ver quem tem razão, como se encontra por aqui e por todos os foruns! E ficamos todos bem. :cheers:

Link to post
Share on other sites

passarito,

Relativamente às imagens, podes alojá-las por exemplo no imgur e depois utilizar o "direct url" com as tags [img ] nos teus posts.

Citação
Que vantagens adicionais trás essa hipótese?

Não é uma questão de vantagens adicionais, mas sim uma questão de clareza.

Da forma que escreveste subentende-se que para utilizar pointers é necessário criar um novo tipo de dados, e isto não é verdade. Nalgumas situações, como na lista que apresentaste, isso é necessário porque não podes utilizar "directamente" um pointer para um record dentro de ele mesmo; mas isto é um caso especial.

Citação
No inicio, é essa a informação que passam nas aulas de programação, mas mais tarde já ouvi dizer que não era bem assim. Já sei que tu, e se calhar mais alguém vai contra-argumentar, portanto vou já adiantar uma coisa.

Pois, eu nunca tive aulas de programação :)

De qualquer forma, sou todo "ouvidos"; gosto de aprender estas coisas.

Ainda assim, tem em conta que os programas fazem uma gestão própria da sua memória interna. Não sei como funcionam o new e o dispose, mas utilizar um programa "externo" para examinar a memória de outro nem sempre resulta, porque os programas podem gerir tanto a sua memória ocupada como a sua memória "livre".

Mas o que se quer é algo de novo para falar :)

Link to post
Share on other sites

Estou a gostar de ver esta saudável discussão sobre os ponteiros. :)

Contudo, preciso de um consenso e algo uniformizado para poder preparar para passar para a Wiki. Se trabalharem em conjunto e apresentarem no tutorial as várias formas de declarar e os casos especiais e tudo o mais, seria melhor. :)


Se tiveres um blog ou assim, @passarito, não será difícil carregar imagens: é utilizar o link directo delas. Todas as minhas imagens e documentos onde meto aqui link estão na Media do meu blog.

Se não, utiliza um site de partilha e alojamento de imagens, como o PhotoBucket.


Espero que este tutorial fique no ponto para poder integrar no Tutorial da Wiki, juntamente com o documento de Passagem por Parâmetro e Referência que estou a preparar, do @nunopicado. Continuação de bom trabalho (e bom diálogo) :)

Knowledge is free!

Link to post
Share on other sites

Eh pá tenho que responder a tantas questões!!!

Pois é, isso das imagens é muito bonito, mas ter de andar de um lado para o outro só para mostrar as imagens... se eu quizer colocar lá meia dúzia delas, vê bem o trabalhão e esses sites só as têm lá durante algum tempo depois ardeu!!! Quanto a Blogs, não tenho nem estou a pensar em ter. Tavez um dia.... também não tenha!!! :) Não sou contra, mas acho que isso não dá comigo, ao fim de um mês aquilo ficava inactivo. É como as contas do Hi5, twiter, facebook... ter tenho, mas mas não passo por lá desde que as criei que foi quando essas coisas apareceram!  :)

Agora vamos ao que interessa....

declaração das variaveis

Eu não disse que não se podia declara da forma que o Pedro-kun referiu, portanto, THOGA31, estás à vontade para aceitar esse tipo de notação. Agora esse tipo de notação só se pode usar no inicio, antes de avançar para modelos mais complexos que dão sentido à existencia e uso dos ponteiros. Nessa altura os ponteiros não têm qualquer vantagem sobre as variaveis normais! Assim, acho que passar-se por cima dessa informação não é grave, antes pelo contrário, mas não estou, nem nunca poderia estar, contra a revelação dessa informação.

Memória

Como prometi no post anterior, vou postar aqui dois programas para verificação da memória. O primeiro tem a ver com o uso e libertação de memória dos ponteiros, o segundo com as variaveis locais.

Não sei se há alguns na wiki iguais, parecidos, ou então muito melhores!!! Sim, é verdade, não verifiquei, mas thoga31, é pá, eu que não tenho pachorra para isso... :knuppel2:

Se não houver podes por na Wiki se não fica aí para quem quizer consultar. Ah, é verdade, e escusas de me dar na cabeça outra vez por caus disto!!!  :smoke:

Program verifica_memoria_usando_ponteiros;

Type
  IntPtr=^Integer;
  RealPtr=^Real;

Var
  IP:IntPtr;
  RP:RealPtr;
  i:integer;

Begin
  Randomize;
  writeln('Mem¢ria disponivel=',memavail:10,' Bytes');
  New(RP);
  New(IP);
  for i:=1 to 15 do begin
    RP^:=Random;
    IP^:=Random(500);
    writeln('RP^=',RP^:10:5,'          IP^=',IP^:5);
  end;
  Writeln('Ap¢s criar variaveis:');
  writeln('Mem¢ria disponivel=',memavail:10,' Bytes');
  Dispose(RP);
  Dispose(IP);
  Writeln('Ap¢s destruir variaveis:');
  writeln('Mem¢ria disponivel=',memavail:10,' Bytes');
  readln;
end.

Segundo programa

Program verifica_memoria_variveis_locais;


Procedure Teste;
Var
  i:integer;
  ai:array[1..15] of integer;
  ar:array[1..15] of real;
begin
  writeln('Mem¢ria disponivel ap¢s entrar no Procedimento:',memavail:10,' Bytes');
  Randomize;
  for i:=1 to 15 do begin
    ar[i]:=Random;
    ai[i]:=Random(500);
    writeln('AR[',i,']=',ar[i]:10:5,'          AI[',i,']=',ai[i]:5);
  end;
  Writeln('Mem¢ria disponivel ap¢s carregar os Arrays: ',memavail:10,' Bytes');
end;


Begin
  Randomize;
  writeln('Mem¢ria disponivel antes do Procedimento: ',memavail:10,' Bytes');
  teste;
  writeln('Mem¢ria disponivel ap¢s sair do Procedimento: ',memavail:10,' Bytes');
  readln;
end.

resultados obtidos com este programa:

Memória disponivel antes do Procedimento:    582368 Bytes

Memória disponivel após entrar no Procedimento:    582368 Bytes

AR[1]=  0.92965          AI[1]=  51

AR[2]=  0.57496          AI[2]=  14

AR[3]=  0.27902          AI[3]=  118

AR[4]=  0.94896          AI[4]=  116

AR[5]=  0.08401          AI[5]=  385

AR[6]=  0.07108          AI[6]=  313

AR[7]=  0.60013          AI[7]=  123

AR[8]=  0.73304          AI[8]=  151

AR[9]=  0.71519          AI[9]=  484

AR[10]=  0.51053          AI[10]=  108

AR[11]=  0.33107          AI[11]=  421

AR[12]=  0.68263          AI[12]=  109

AR[13]=  0.72305          AI[13]=  58

AR[14]=  0.58964          AI[14]=  140

AR[15]=  0.86102          AI[15]=  460

Memória disponivel após carregar os Arrays:    582368 Bytes

Memória disponivel após sair do Procedimento:    582368 Bytes

Pedro-Kun:

Deverás ficar surpreendido com estes resultados, na verdade eu também, pois sabia das duas vertentes, mas, por um lado é licito pensar que as variaveis locais só são carregadas na memória quando chamadas, mas por outro lado o programa quendo é executado tem que reservar espaço para as suas variaveis.

O que estou a chegar à conclusão, e que as variaveis locais apenas têm sentido na organização do programas e não na memória.

Link to post
Share on other sites

passarito,

Não vou fingir que conheço a fundo a forma como a memória é gerida num programa Delphi/Free Pascal.

Mesmo assim, tenho um palpite: os resultados que obtiveste no 2º programa devem-se ao facto das variáveis locais serem alocadas na stack e o memavail medir o espaço livre na heap (onde ficam as variáveis de alocação dinâmica).

A stack tem um tamanho fixo dependente do compilador (e suas definições) e do sistema operativo.

A heap tem um tamanho inicial definido, mas pode crescer ao longo do programa -- no 2º programa tu mexeste na stack e mediste a heap :)

Relativamente ao que disseste das variáveis serem alocadas no início da execução, isso não faz sentido, independentemente da forma como funciona a alocação e libertação de memória, e vou dar-te um exemplo disso.

Imagina uma função recursiva que aceita um parâmetro (um integer, de 4 bytes (32bit)). Se esta função se chamar a si própria, vai ter que alocar na stack nova memória para armazenar os parâmetros das chamadas subsequentes. Isto é uma coisa que não pode ser pré-alocada ou definida pelo compilador, porque só durante a execução saberemos quantas chamadas recursivas serão feitas.

PS.: Por acaso desconhecia a função do comando memavail mas uma pesquisa rápida disse-me que além de medir o espaço livre na heap, o memavail não é indicado para fazer essas medições em ambientes modernos (e no Free Pascal os teus exemplos nem compilam porque eles removeram esses comandos).

Link to post
Share on other sites

Estive a pesquisar um pouco mais, e acho que tens razão... UFA... Eu, como disse antes, também achava estranho. Ainda por cima, antigamente tinha-se tanto cuidado com a memória, pois esta não abundava como nos tempos de hoje. O meu primeiro PC a sério, um 486 - 33MHz, tinha apenas 8MB de memória. Agora Há com 8GB!!!!

Alterei os dois primeiros parágrafos, vejam lá se assim já concordam! Penso que não será necessário aprofundar mais o tema!

Quanto às versões de Pascal, como já disse anteriormente, eu uso o Borland Pascal 7. O último original. para o melhor e para o pior. E não vou mudar!

Já agora... e TSR's? Alguem ainda programa isso? Isso ainda funciona no windows, ou com o aparecimento do windows os TSR's deixaram de funcionar?

Link to post
Share on other sites
nunopicado

TSR - Terminate and Stay Resident

Chiça, ainda me lembro menos disso do que dos ponteiros, e olhem que já não me lembro nada disto...

Passarito, o meu primeiro era um IBM com 2MB de RAM e aqui há tempos vendi um servidor, com material a pedido do cliente, com 64GB de ram....

Por isso olha... Lindo mundo, este em que vivemos! lol

"A humanidade está a perder os seus génios... Aristóteles morreu, Newton já lá está, Einstein finou-se, e eu hoje não me estou a sentir bem!"

> Não esclareço dúvidas por PM: Indica a tua dúvida no quadro correcto do forum.

Link to post
Share on other sites

Não digas... um IBM 286 PS/2 ?

Eh pá... que recordações. Trabalhei com um na empresa onde ainda hoje trabalho... aquilo eram umas bombaças!!! LOL

Mas deixa-me contar uma história que é uma reliquia.

A minha mulher fez o curso na Faculdade de Ciencias de Lisboa e uma vez ela tinha um trabalho com uma colega e lá fui eu ter com ela. Deparei-me com uns computadores IBM 286, mas sem disco rigido... então como é que aquilo funcionava? Tinha 2 drives de disketes, num colocavam a diskete com S.O. no outro a diskete com o processador de texto, word 5 acho eu, e com o texto. ALTOS TEMPOS!!!!

Mas ainda tem mais, elas não sabiam por a numeração nas páginas, ainda por cima tinham um documento para cada capitulo. Então eu fiz-lhes um programaita em Pascal, que depois compilei para executavel, para elas utilizarem. Um programa avançadissimo. Chamava-se NUMERA, e servia para numerar páginas a partir do número que se quizesse. Até dava em numeração romana até ao 20, acho eu! Aquilo era bonito, imaginem, impressora de agulhas (impressoras da altura), colocavam e posicionavam a folhinha, carregavam no botão da impressora esta puxava a folha quase até ao fim e.... trrraasss.... número impresso!!!! ESPETACULO!!!!

Por acaso perdi o rumo a esse programa senão postava-o aqui só para a reliquia ficar registada para a eternidade!!!

Mas voltando aos TSR, oh Nuno aquilo era lindo... por um relogio sempre visivel e a contar enquanto estavamos no DOS... era um gozo mostrar aquilo aos amigos!!!!

Link to post
Share on other sites

passarito,

De facto, hoje em dia o cuidado com a memória é pouco. Mas se reparares, o Pascal sempre encorajou essa "falta de preocupação" (p. ex., criar uma string implica a alocação imediata de 256 bytes, a menos que se defina outro valor manualmente).

De resto, eu não sou do tempo de nenhuma dessas coisas dos TSR (tinha eu o meu primeiro computador... um 486 emprestado, com DOS e Win3.11 já em 1997), mas os TSR efectivamente perdem a sua utilidade quando estamos a falar de SOs com multitasking, pelo que estive a ler.

Voltando ao tópico, há diversas situações em que podemos utilizar pointers sem criar uma nova definição de tipo de dados... Tudo o que não envolva utilizar esse tipo de dados dentro de si mesmo, já agora (nem todas as estruturas de dados são recursivas).

Link to post
Share on other sites
nunopicado

Não digas... um IBM 286 PS/2 ?

heheheh não, nem tanto!

O meu era um PS/1 386SX 25MHz, já com um estrondoso disco de 85MB (chamaram-me maluco na escola por ter comprado aquilo, que nunca na vida o iria encher 😄 - o normal eram 20MB, alguns melhorzinhos com 40MB -- A proposito, para os leitores mais novinhos, não me enganei, são mesmo MB e MHz 😄 ).

2MB de RAM eram o limite (limite mesmo, acrescentar para os 4MB custava 80 contos - 400€, já que tinham de ser modulos IBM e eles preferiam vender um PC novo do que por memoria naquele :) ).

MS-DOS 5.0, Windows 3.1 e Works 2.0 para DOS... Ainda trabalha... Ou melhor, trabalhava a ultima vez que o liguei!

Muitas horas lá passei a programar em pascal!

Dos TSR's, eram o mais parecido com multi-tarefa que havia! Hoje estão obsoletos!

"A humanidade está a perder os seus génios... Aristóteles morreu, Newton já lá está, Einstein finou-se, e eu hoje não me estou a sentir bem!"

> Não esclareço dúvidas por PM: Indica a tua dúvida no quadro correcto do forum.

Link to post
Share on other sites

Nuno,

só por curiosidade... o teu monitor já era policromático?

Eu cheguei a trabalhar com uns monocromáticos, mas a laranja/preto e verde/preto... para cada uma das cores havia um nome especifico que agora não me lembro!

Link to post
Share on other sites
nunopicado

O meu era...

Esses monocromáticos "coloridos" eram os Hércules. Cheguei a trabalhar com um também!

"A humanidade está a perder os seus génios... Aristóteles morreu, Newton já lá está, Einstein finou-se, e eu hoje não me estou a sentir bem!"

> Não esclareço dúvidas por PM: Indica a tua dúvida no quadro correcto do forum.

Link to post
Share on other sites

Boas, antes mais está aqui um bom tutorial  :P

Quando vi este tópico tentei fazer um programa com listas ligadas mas não consegui perceber muito bem como se fazia, será que alguem poderia apresentar outro exemplo e explicar melhor (se não for pedir muito :dontgetit:)

Comprimentos,

HelderT

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.