Ponteiros - Pascal - Portugal-a-Programar
October 14, 2011 in Pascal
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.
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
IntPtr=^Integer;
V1: IntPtr;
No caso de querem 3 variaveis do tipo IntPtr declarariam assim:
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
Write('Indique um número: ');
Readln(v1^);
V1^ := v1^ + 5;
Write(' O seu número adicionado de 5 unidades:  ',V1^);
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.
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:
Ptr = ^Rec;
Marca: string[25];
Tamanho: Integer;
NextPtr: Ptr; {Serve para nos indicar o campo seguinte. Se for o último deve apontar para NIL}
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);
Inserindo um Registo B
Então pegando na declaração anterior, vamos inserir um registo numa lista ligada ordenada pela marca.
Procedure le_insere_reg;
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}
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;
NovoReg^.NextPtr:=Aux1;
RegAnterior^.NextPtr:=NovoReg;
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!
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
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.
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:
Relativamente às imagens, podes alojá-las por exemplo no imgur e depois utilizar o "direct url" com as tags [img ] nos teus posts.
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.
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
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)
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!
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.
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;
RealPtr=^Real;
IP:IntPtr;
writeln('Mem¢ria disponivel=',memavail:10,' Bytes');
New(RP);
RP^:=Random;
IP^:=Random(500);
writeln('RP^=',RP^:10:5,'          IP^=',IP^:5);
Writeln('Ap¢s criar variaveis:');
Dispose(RP);
Writeln('Ap¢s destruir variaveis:');
Program verifica_memoria_variveis_locais;
ai:array[1..15] of integer;
ar:array[1..15] of real;
writeln('Mem¢ria disponivel ap¢s entrar no Procedimento:',memavail:10,' Bytes');
ar[i]:=Random;
ai[i]:=Random(500);
writeln('AR[',i,']=',ar[i]:10:5,'          AI[',i,']=',ai[i]:5);
Writeln('Mem¢ria disponivel ap¢s carregar os Arrays: ',memavail:10,' Bytes');
writeln('Mem¢ria disponivel antes do Procedimento: ',memavail:10,' Bytes');
writeln('Mem¢ria disponivel ap¢s sair do Procedimento: ',memavail:10,' Bytes');
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.
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).
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?
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
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!!!!
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).
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!
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!
O meu era...
Esses monocromáticos "coloridos" eram os Hércules. Cheguei a trabalhar com um também!
Boas, antes mais está aqui um bom tutorial
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:)
Diz lá qual é a tua duvda em concreto... é que eu coloquei lá um procedimento para criar uma lista ligada
A dúvida continua aqui
Relembro que as dúvidas devem ser tiradas em tópicos novos. Aqui apenas se deve falar sobre o tutorial em si.