|
|
Punteros
Lo primero: hay que entender muy bien lo que es un puntero, porque al principio
puede se run poco complicado. Un puntero es un tipo de datos (como un INTEGER,
por ejemplo) que lo que almacena no es ni un n�mero ni una letra sino una
direci�n de memoria. Una posici�n de memoria es una "cosa" del tipo
0xxxxh:0xxxxh que identifica una posici�n de la memoria CONVENCIONAL
del ordenador. (Recordemos que el TP 7 s�lo sabe hacer programas para
286, o sea: para modo real, por lo tanto s�lo podemos usar la memoria
convencional [esto en realidad es falso, se puede usar memoria XMS y EMS
desde Pascal, pero eso ya es otro tema]) Se dice que un puntero APUNTA a
la direci�n de memoria.
Para declararlo tenemos el tipo POINTER, con lo que lo declaramos igual que
cualquier otro tipo de datos:
Var
Puntero : Pointer;
�Y esto para qu� nos sirve? Pues as�, con lo que sabemos ahora, de nada. Pero
lo curioso de los punteros es que podemos decirle qu� es lo que hay en la
posici�n de memoria a la que est� apuntando. Me explico: el puntero "se�ala"
a un lugar de la memoria de nuestro PC. Pues ese lugar podemos tratarlo como
un BYTE, o como un WORD, como un STRING, como una estructura de datos o
incluso como otro puntero!
En este caso la declaraci�n cambia. En este caso se declara igual que si
declar�semos una variable de un tipo concreto s�lo que precedemos el nombre
del tipo por el s�mbolo ^. (ALT+94)
As� para declarar un puntero de manera que la memoria a la que apunta sea
tratada como un byte har�amos esto:
Var
Puntero : ^Byte;
Y lo mismo para una estructura:
Type
Tipo = record
Nombre : String;
DNI : Longint;
NIF : Char;
Calle : String;
Edad : Word;
End;
Var
Puntero : ^Tipo;
Este tipo de punteros se denominan de una manera especial, son punteros A el
tipo de datos. Es decir, este �ltimo ejemplo ser�a un puntero A una
estructura (o a la estructura Tipo), el anterior ser�a un puntero a un byte,
etc... Y tambi�n pueden hacerse punteros a punteros, punteros a arrays de
punteros, etc... pero eso es m�s complicado y vendr� m�s adelante.
Pero... �a d�nde apunta un puntero? Eeeeiiinngg? ;) Me refiero: un puntero,
nada m�s empezar el programa �apunta a alguna parte? �a d�nde apunta? Pues
lo m�s normal es que apunte a la direci�n 0000:0000, ya que las variables
suelen inicializarse a cero. Por lo tanto antes de usar el puntero tenemos
que hacer que apunte al sitio que nosotros queremos. �Qu� pasa si no lo
hacemos? Pues si modificamos un puntero que apunta a 0000:0000... en esa
zona de la memoria est� la tabla de los vectores de interrupci�n, si lo
modificamos nos cepillaremos esa tabla, con lo cual, en cuanto se genere
una interrupci�n del temporizador (una cada 55 milisegundos) nuestro
programa nos brindar� un precioso cuelgue.
Este tipo de punteros puede ser m�s �til, porque... Ya que un puntero
puede apuntar a cualquier parte de la memoria convencional... podemos
modificar cualquier posici�n de memoria, tanto si pertenece a nuestro
programa como si no. Por ejemplo, el WORD de la posici�n 040h:02h contiene
la direci�n del puerto base para el COM2, para leerla s�lo tenemos que hacer
que un puntero a un word apunte a esa direci�n (040h:02h) y despu�s leer lo
que hay all�. Para hacer que un puntero apunte a un sitio determinado tenemos
la funci�n Ptr. La funci�n Ptr acepta como par�metros el segmento y el offset
de la direci�n de memoria y nos devuelve como resultado un puntero a esa
direci�n. As�, es programa que lee la direci�n del COM2 quedar�a as�:
Var
Puntero_a_COM2 : ^Word;
Begin
Puntero_a_COM2 := Ptr($40,2);
WriteLn('La direci�n del COM2 es ',Puntero_a_COM2^);
End.
Una cosa importante a tener en cuenta es que no es lo mismo la direci�n de
memoria que lo que hay en esa direci�n. El puntero s�lo contiene la direci�n,
no contiene lo que hay en ella. Es decir: el puntero s�lo se�ala al WORD donde
est� la direci�n del COM2. El puntero NO almacena la posici�n del COM2. Por
eso estas dos lieas son bien diferentes:
Puntero := 8;
Puntero^ := 8;
La primera intenta asignar el valor 8 como direci�n de memoria a la cual
tiene que apuntar el puntero. Mientras que la segunda lo que hace es asignar
el valor 8 a la direci�n de memoria a la que apunta el puntero. Es muy
importante que esto quede claro.
Podr�amos decir que Puntero^ es el elemento y Puntero es la direci�n del
elemento.
Pero la utilidad de los punteros no acaba aqu� ni mucho menos. Creo que ya
antes hab�a dicho que las variables de un programa s�lo pod�an ocupar 64 Kbs
como mucho. Y... �qu� ha pasado con en resto de los 640 Kbs de memoria? Pues
esa memoria se puede usar mediante punteros.
Para pedir memoria tenemos la funci�n Getmem y para liberarla una vez ya no
la necesitamos tenemos Freemem. A ambas hay que pasarles un puntero y el
tama�o de memoria que queremos reservar. Por ejemplo:
Getmem(Puntero, 30000);
Esto lo que har�a ser�a reservar 30000 bytes de memoria y hacer que el
puntero apunte al inicio de esos 30000 bytes. �A qu� tipo de datos tiene
que apuntar este puntero? A cualquiera. Si hemos declarado Puntero como un
puntero a un byte podremos usar "Puntero^ := valor;" para modificar el primer
byte del bloque de memoria que hemos pedido.
�Y qu� pasa con el resto? Pues muy sencillo. Podemos "mover" el puntero para
que apunte al segundo byte del bloque, o al tercero... Para eso tenemos las
funciones INC y DEC. Con INC(PUNTERO); hacemos apuntar el puntero a el
siguiente elemento. PERO OJO!!! El siguiente elemento no siempre ser� el
siguiente byte. Si hemos definido el puntero como un puntero a words cada
vez que usemos INC avanzaremos 2 bytes (que es lo que ocupa un WORD). Y DEC
se usa igual y tiene la funci�n inversa.
La pega que tiene este m�todo es que nadie nos impide hacer 600.000 INCs y
poner el puntero apuntando a una parte de la memoria que no hemos reservado.
Y es que para bloques de memoria hay maneras m�s pr�cticas de manejarlos.
Para esto son muy �tiles los punteros a arrays. Podemos definir un as�:
Type
P = Array [0..29999] of Byte;
Var
Puntero : ^P;
Begin
Getmem(Puntero, 30000);
End.
De esta manera definimos un array que empieza en la direci�n a la que apunta
el puntero. Esta direci�n, despu�s del Getmem, ser� la direci�n del bloque
de memoria. Y a partir de esa direci�n tenemos 29999 elementos m�s en el array.
Cada elemento es un Byte, por lo tanto, a cada posici�n del array le
corresponde un byte del bloque de memoria que hemos reservado.
Ahora, para leer/escribir en ese bloque s�lo tenemos que hacer cosas como
esta:
Puntero^[8] := Puntero^[15003] - 80;
F�cil �no? Pero... �qu� pasa si queremos poner todo el bloque a cero? Podr�amos
hacer un bucle FOR que fuese poniendo las posiciones a cero una por una, pero
hay una manera mejor. Tenemos la instruci�n FillChar. Esta instruci�n llena con
un valor dado el n�mero de posiciones que le digamos a partir de la posici�n
que le demos. Se usar�a as�:
FillChar(Puntero^, 30000, 0);
Atenci�n a que ponemos "Puntero^" y no "Puntero". �Por qu�? Pues porque lo que
queremos llenar de ceros no es el puntero sino la memoria a la que apunta ese
puntero. (Ya vereis que cuelgues m�s majos os salen cuando se os olvide esto ;)
Y... �qu� pasa si tenemos 2 bloques de memoria y queremos copiar todo lo que
haya en uno al otro? Pues podr�amos hacer un FOR, pero hay una funci�n
espec�fica para eso: Move. Move se usar�a as�:
Move(Puntero_Fuente^, Puntero_Destino^, Tama�o);
Cuando ya hayamos hecho todo lo que quer�amos con un bloque de memoria debemos
liberarlo. Para eso usamos Freemem igual que si fuese GetMem: Freemem(Puntero,
Tama�o). Hay que tener en cuenta que el puntero debe apuntar exactamente al mismo
sitio que despu�s de hacer el GetMem. Es decir: si hemos usado alg�n INC o DEC
con �l debemos asegurarnos que lo dejamos tal y como estaba antes de llamar a
Freemem.
Ahora supongamos que tenemos un puntero que apunta a una estructura y queremos
reservar la memoria justa para que quepa esa estructura. Podemos contar los bytes
que ocupa esa estructura (no estar�a mal para practicar ;) o podemos usar New en
lugar de Getmem. A New s�lo hay que pasarle el puntero: New(Puntero);
Y el equivalente a Freemem entonces ser� Dispose, que usaremos igual que New.
Y ahora el punto m�s interesante. Antes de empezarlo os presento a NIL. NIL es
una constante, pero no es una constante num�rica sino un puntero. Y �a d�nde apunta
este puntero? En la ayuda dice que no apunta a ninguna parte. Y... aunque un
puntero SIEMPRE apunta a alguna parte haremos como si fuese verdad que no apunta
a nada. As� podemos decir que NIL es el puntero nulo.
Ahora empecemos: declaramos un puntero as�:
Type
PEsctructura = ^Estructura;
Estructura = record
Nombre : String;
Siguiente : PEstructura;
End;
Var
Puntero : PEstructura;
Bien... y reservamos memoria para el puntero:
New(Puntero);
En este momento ya tenemos un puntero apuntando a una estructura que consta de
una cadena de caracteres y de otro puntero.
Rellenamos el primer campo:
Puntero^.Nombre := 'Pepito';
Y pedimos memoria para el segundo (ya que un puntero no podemos usarlo si no
hemos pedido memoria para �l o le hemos hecho apuntar a alguna parte).
New(Puntero^.Siguiente);
Ahora el puntero Siguiente apunta a una estructura de datos que consta de 2 campos:
una cadena de caracteres y un puntero. �os suena de algo? ;)
Podemos volver a pedir memoria para el puntero de esta segunda estructura y luego
pedir memoria para el puntero de la tercera, etc, etc...
�Y qu� ventaja tiene esto? Pues que podemos irlo repitiendo todas las veces que
queramos hasta que se nos acabe la memoria. En un array hay que definir el n�mero
de elementos a la hora de escribir el programa, pero de esta manera podemos ir
llenanado m�s y m�s elementos sin tener un l�mite prefijado.
La pega ahora es... �C�mo se trabaja con esto? Pues bien, para dejarlo lo m�s
claro posible os pongo un ejemplo sencillo:
Type
P2 = ^Estruc;
Estruc = record
Cadena : String;
Siguiente : P2;
End;
Var
Puntero, PAux : P2;
Cadena : String;
Begin
WriteLn('Memoria libre: ',maxavail);
WriteLn('Escribe nombres. (una linea en blanco para terminar)');
New(Puntero);
PAux := Puntero;
PAux^.Siguiente := Nil;
Repeat
ReadLn(Cadena);
PAux^.Cadena := Cadena;
if Cadena <> '' Then
Begin
New(PAux^.Siguiente);
PAux := PAux^.Siguiente;
PAux^.Siguiente := Nil;
End;
Until Cadena = '';
WriteLn;
WriteLn('Has escrito:');
PAux := Puntero;
While PAux^.Siguiente <> Nil do
Begin
WriteLn(Paux^.Cadena);
Puntero := Paux;
PAux := Paux^.Siguiente;
Dispose(Puntero);
End;
Dispose(Paux);
WriteLn('Memoria libre: ',maxavail);
End.
|
|