Está en la página 1de 10

Programaci on Modular. ETSIT. 1o C.

Apuntes del profesor Juan Falgueras Cano Curso 2005

2 Memoria din amica


Contenido
2. Memoria din amica 2.1. Gesti on din amica de la memoria . . . . . . . 2.1.1. Punteros . . . . . . . . . . . . . . . . . 2.2. Punteros en pseudoc odigo . . . . . . . . . . . 2.2.1. Utilidad de los punteros . . . . . . . . 2.2.2. Solicitud din amica de memoria . . . . 2.2.3. C++ . . . . . . . . . . . . . . . . . . . 2.2.4. Arrays din amicos . . . . . . . . . . . . 2.3. Estructuras de datos recursivas . . . . . . . . 2.4. Listas lineales de de nodos con simple y doble 2.5. Listas posicionales y ordenadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . enlace, cabeceras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 4 4 4 5 6 6 7 9

2.

Memoria din amica


Hasta ahora todos los tipos de datos predenidos (enteros, reales, caracteres, booleanos) y denidos por el usuario (enumerados, y estructuras de datos arrays y registros) han sido de tipo est atico. Qu e signica esto? Que su ubicaci on y capacidad de almacenamiento quedan r gidamente denidos desde el momento en que se declaran y no puede cambiar durante el funcionamiento del programa. Esto tiene la ventaja obvia de ser muy f aciles de localizar en la memoria interna y tambi en de simplicar y aligerar el c odigo de los programas ejecutables. Con los punteros las cosas no cambian: son est aticos. Una variable de tipo puntero es una variable est atica, pero. . . la memoria a la que apuntan puede que no. Vamos a ver en este tema c omo se puede acceder directamente a la memoria interna libre del sistema y c omo se pueden conseguir y devolver montones de memoria seg un se necesiten a lo largo de la ejecucion de partes del programa.

2.1.

Gesti on din amica de la memoria

Las variables en los programas se declaran mediante un tipo (modo de codicaci on) y un nombre de variable. As para nosotros una variable es un identicador que se reere a un espacio reservado de una manera denitiva (est aticamente) por el compilador en alg un lugar (que hasta ahora no nos ha interesado). 2.1.1. Punteros

Un puntero es una variable est atica (como las vistas hasta ahora) pero destinada a guardar direcciones de memoria de otras varibles. Lo que esto signica es que las direcciones de memoria pueden ser guardadas y manipuladas en nuestros programas. (Ver Figura 2) Una variable puntero (que no lo olvidemos, son en realidad diest aticas siempre) puede guardar una direcci on de memoria. Las cuestiones ahora son: QUE guardar recci on de memoria? , COMO obtener direcciones de la memoria interna?, PARA QUE esas direcciones?

2.1

Gesti on din amica de la memoria

a=33;

33

? 3 2 1 0

Figura 1: Hasta ahora no nos hemos preocupado de saber d onde el compilador ubicaba las variables est aticas que hemos usado.

3FA08 *p *p 33 33 3FA04 3FA00

3FA04 p 3FA04 001A2 00000

Figura 2: Aunque un puntero es una variable monol tica que apunta a una zona de memoria con otras variables; el mismo, el puntero es tambi en una variable m as en memoria.

Para responder a estas preguntas veremos primero un m etodo b asico para obtener direcciones de memoria; despu es veremos un sistema de localizar bloques de memoria y nalmente, con esto habr an quedado respondidas las tres preguntas. No todos los lenguajes de programaci on se arriesgan a permitir la gesti on directa de la memoria mediante punteros1 . En C/C++: typedef int *TPuntEntero; // o int* TPuntEntero; /* variables */ int *punt; TPuntEntero a, b; es lo mismo

Una cuesti on muy importante respecto a los punteros, y que algunos la toman como diferencia entre mera direcci on y puntero de verdad, es el hecho de que un puntero siempre lleva asociado un tipo al que apuntar. Esto es, un puntero no es s olo una direcci on de memoria interna, para el compilador, tambi en asociamos con esas direcciones un tipo de datos. Esto va a ser fundamental para ayudar al compilador a disciplinar nuestro programa, en el sentido de que si declaramos un puntero a entero, no podemos despu es asignarle la direcci on de un n umero real, por ejemplo. En principio los valores de las direcciones, como las direcciones de cualquier gu a de tel efonos, por ejemplo, son an onimas y meras referencias geogr acas, en el caso del ordenador, meros n umeros posiciones de memoria. Sin embargo, el intercambio de contenidos entre dos direcciones, por ejemplo, debe hacerse sabiendo que los contenidos de aqu ellas direcciones son compatibles, del mismo tipo. Adem as veremos en alguna ocasi on la utilidad de poder hacer aritm etica sencilla con los punteros: sumarle/restarle un n umero entero. Al estar el puntero ligado a un tipo concreto de datos, este aumento/disminuci on de la posici on se har a correctamente: el valor del puntero a un
1 decimos se arriesgan porque lenguajes m as modernos (como por ejemplo Java) evitan el uso de punteros por muchos motivos, entre ellos, el de que el uso de punteros es bastante dif cil de proteger, de manera que y el programador puede f acilmente, con peque nos errores destruir todo el estado de un sistema o acceder a cualquier parte protegida del mismo.

RAM

2.1

Gesti on din amica de la memoria

entero aumentado en uno se incrementar a en realidad en el tama no de un entero apuntando al siguiente entero (ver Fig. 3).

*(p+1) *p p

321 33 3FA04

3FA08 3FA04 3FA00

00000

Figura 3: Aritm etica de punteros. Si sumamos un entero a un puntero la nueva direcci on tiene en cuenta el tama no del tipo de elemento apuntado.

Una vez creada una variable o tipo puntero se inicializar a, se le dar an valores: punt = 0; punt = &i; punt = &ficha; Un valor muy especial en los punteros es el valor 0 (NULL). El valor 0 en los punteros es la indicaci on de que no se apunta a nada. Aunque la direcci on de memoria 0 existe, no se usa y en punteros es la indicaci on b asica de que no hay puntero. En los diagramas un puntero NULL se suele dibujar como un enlace a tierra de electricidad. Ver Figura 4
p 0

Figura 4: Puntero NULL: enlace a tierra, no apunta a nada.

Hemos usado en el ejemplo anterior el operador &. El operador & es un potente operador de C/C++ que sirve para obtener la direcci on de memoria de cualquier elemento. Por ejemplo, en C/C++: int x; int *p = &x; signica que a la vez que creamos la variable puntero p, que es un puntero a entero, le podemos asignar la direcci on d onde el compilador ha situado a otra variable, tambi en de tipo entero. El tener guardada la direcci on ser a algo bastante in util si tan s olo fuese eso, una direcci on que se guarda, la segunda parte es acceder a esa direcci on. Esto es, nos interesa conocer qu e es lo que hay en el sitio al que apunta un puntero. Si no tuviesemos esta posibilidad, tan s olo habr amos guardado la direcci on para nada. Para acceder a lo apuntado por una direcci on, por un puntero, debemos desreferenciarlo o indireccionarlo leer lo apuntado por el. Para acceder a lo apuntado por un puntero los lenguajes de programaci on disponen de un operador de desreferenciaci on, poniendo el operador delante del puntero se entiende que lo que queremos es la informaci on que hay en la direcci on escrita en ese puntero. En C/C++, siguiendo el ejemplo anterior, *p = x;

2.2

Punteros en pseudoc odigo

copia el contenido de la variable x al lugar (de tipo entero) al que apunta la variable puentero p. Naturalmente podemos escribir el contenido de la variable x de la forma m as sencilla como siempre x = 3; El resultado es equivalente en estos casos.

2.2.

Punteros en pseudoc odigo

Puntero <TipoApuntado> *PTI; // NODO *ENLACE, *LINK; // N *arrPunt[1..10]; // array de 10 punteros (raro) NOTA: en pseudolenguaje, el contenido de las estructuras se puede copiar, sin embargo no los arrays. Ejemplos: N *puntero_a_natural; //tipo puntero N array_natural[1..20]; //array de 20 naturales enteros 2.2.1. Utilidad de los punteros

Qu e utilidad puede tener el anterior uso de las variables de tipo puntero? Hasta ahora la introducci on de la direcci on de las variables y zonas de memoria con los punteros no a nade ninguna ventaja, excepto en el lenguaje C, d onde es el u nico mecanismo posible para poder modicar par ametros: pasar una copia de la direcci on a la funci on y en esta indireccionar y modicar el elemento pasado a trav es de su direcci on. Un uso fundamental es el que se da en los lenguajes en los que no existe un paso de par ametros por referencia, como le ocurre, por ejemplo al lenguaje C, se recurre al siguiente mecanismo: a la funci on/procedimiento se le pasa una copia (siempre) de la direcci on de la variable (&) y en la funci on se opera con lo apuntado por esa direcci on (*): de esa forma se accede al contenido de la variable que se haya pasado. Este mecanismo ya es conocido por nosotros. Aparte de este modo de uso el conocer las direcciones de las variables est aticas de los programas no tiene ning un otro inter es ya que para modicarlas, leerlas, etc, bastar a como siempre con usar su nombre, la etiqueta que los compiladores permiten crear para cada variable est atica: x = 3; Sin embargo, la segunda forma de obtener direcciones de memoria es la m as interesante y la que nos ocupar a en este tema. 2.2.2. Solicitud din amica de memoria

El principal uso de los punteros est a en liberar la rigidez de los espacios de memoria para datos. Las variables est aticas no pueden crecer durante la ejecuci on de un programa y, si nos hace falta m as espacio para guardar m as datos durante la ejecuci on de un programa, necesitaremos algo nuevo. Los lenguajes que utilizan punteros aportan funciones que localizan y reservan para nuestro programa tama nos solicitados de memoria interna. Para ello los programas tienen una zona de memoria libre propia (aunque tambi en en los sistemas m as modernos esta memoria se pide a la del sistema, que adem as es paginable y compartible con memoria virtual de disco) llamada arena situada entre la pila y los datos est aticos. Ver Figura 5 En C el mecanismo que se utiliza es el de decir la cantidad de memoria que se necesita a una funci on y esta devuelve una direcci on, la direcci on del comienzo del lugar d onde ha reservado ese

2.2

Punteros en pseudoc odigo

cdigo PROGRAMA datos inicializados datos no inicializados arena pila de programa

Figura 5: Zona de memoria libre de un programa, que sirve para ser usada y reusada din amicamente durante su ejecuci on. Otras zonas son las de datos inicializados: constantes y cadenas de mensajes, etc. . . y la zona que durante la ejecuci on y de manera est atica tienen reservadas las variables. La pila es la zona, din amica tambi en, que se usa para el paso de par ametros a las funciones.

espacio de memoria2 . Para ello el lenguaje C aporta b asicamente dos operadores para la gesti on din amica de la memoria: malloc y free. La primera es la que hace las reservas, la segunda es la que las libera, las devuelve al sistema3 . Por ejemplo, si queremos que el sistema localice espacio para un n umero de tipo entero, pedimos espacio para un n umero entero. sizeof(int) es una funci on interna de C que dice el espacio en bytes que ocupa cualquier tipo. Por otro lado la funci on malloc(..) s olo requiere conocer ese dato, la cantidad de bytes que se necesitan. malloc busca espacio libre, los bloquea, y devuelve la direcci on d onde lo ha encontrado: p = malloc(sizeof(int)); p = malloc(10000); reservar a 10.000 bytes de memoria que comenzar an en p. Si malloc no encuentra m as memoria libre devuelve una direcci on nula: nul o NIL o 0. Una vez que hayamos operado con la direcci on (con el contenido al que apunta esa direcci on, en realidad), debemos liberarla. La liberaci on de los bloques pedidos al sistema es esencial si el programa ha de seguir iterando y pidiendo en otros sitios nuevos bloques. No siempre es as y el sistema usualmente libera todo lo prestado al programa al terminar. Pero en general es una t actica muy sana el liberar los bloques de memoria que ya dejan de ser necesarios. La funci on en C para realizar esto es free. free s olo requiere conocer el lugar d onde comienza el bloque antes prestado. Curiosamente no requiere que se le indique cu anto ocupaba aquella reserva: free(p); vuelve a dejar ese sitio disponible para futuras necesidades de meroria. 2.2.3. C++

C++ utiliza un operadores m as complejos new que deduce el tama no necesitado y modica el par ametro puntero que se le pone. y delete para liberar esta memoria. Estos operadores son los mismos que se utilizan para crear objetos din amicamente y permiten adem as la inicializaci on de los elementos: Por ejemplo
2 dado que los punteros llevan asociados el tipo de datos al que apuntan, en muchos lenguajes no es necesario decir la cantidad de memoria que queremos reservar sino s olo para qu e tipo de dato queremos apuntar, y el compilador deduce el espacio de memoria que se necesita y asigna esa direcci on al puntero. 3 Para ver las dem as funciones que aporta el sistema, ver la cabecera: <stdlib.h>.

2.3

Estructuras de datos recursivas

int *pint, *parr; pint = new int; parr = new int[10] // ... delete pint; delete[] parr; N otese que en C++ la solicitud es siempre por unidades l ogicas: se pide un entero, una cha, un car acter; o bien se pide un array de n elementos. En el u ltimo caso se tiene que liberar con la sintaxis especial delete[] loquesea; En C++ el operador new puede llevar despu es del tipo que crea un valor para inicializarlo: double *p; p = new double (3.1416); 2.2.4. Arrays din amicos

Una inmediata ventaja de la creaci on de elementos din amicamente es la posibilidad de crear arrays del tama no que nos haga falta durante la ejecuci on del programa: int n; cout << "introducir n umero de elementos: "; cin >> n; double *datos; datos = new double[n]; // quedan inicializados a 0 for (int i=0; i<n;i++) cout << datos[i] << ", "; cout << endl; En todos los casos, cuando sepamos que no se van a utilizar m as estas estructuras debemos liberarlas: delete[] datos;

2.3.

Estructuras de datos recursivas

Aunque se pueda localizar un bloque de memoria, el problema es que no se sabe cu antos de esos bloques de memoria necesitaremos. Si un programa requiere diez bloques de memoria en una ejecuci on habr a que tener previstas diez variables de tipo puntero para guardar las direcciones de estos bloques. Pero si durante la ejecuci on se hubiesen requerido veinte, tendr amos un problema. La soluci on est a en a enganchar un bloque con el siguiente (nuevamente mediante punteros) y formar as cadenas de bloques todos ligados y acceder a ellos mediante una direcci on, un puntero, el puntero al primer bloque. Para poder formar estas cadenas de bloques lo que se hace es denir una estructura de datos como (en C++) (ver Fig 6): struct TNodo { int dato; TNodo *sigui; }; o, para facilitar su uso, typedef TNodo *TEnlace; struct TNodo { int dato; TEnlace sigui; };

2.4

Listas lineales de de nodos con simple y doble enlace, cabeceras

Es la primera vez que estamos deniendo una variable (un tipo m as bien) o un objeto cualquiera en funci on de otro a un no denido. Esto es necesario as por la propia recursividad de la estructura. Un enlace es un puntero a algo que tiene un enlace a lo que estamos a un deniendo.

Lista

lista

Figura 6: Lista simple: tenemos un puntero lista que apunta a un nodo el cual tiene un puntero que apunta al siguiente, etc etera.

La cuesti on ahora es que cada vez que se pide un bloque para un nodo tambi en se est a ganando espacio para guardar la direcci on del siguiente nodo o bloque. De esta forma, de un s olo puntero podemos colgar la cantidad de nodos que nos vayan haciendo falta durante el programa. Dependiendo del criterio utilizado para enganchar esos nodos tendremos estructuras lineales o no lineales m as o menos complejas pero que permitir an acceder a los nodos seg un nos interese. Cuando se acaba la secuencia de nodos y ya no se tienen m as nodos detr as, se utiliza como indicador de siguiente el valor de puntero nulo. De otra manera, el u ltimo no tiene a nadie detr as y su siguiente es NULL. Se deja como ejercicio la construcci on de una estructura lineal o lista de nodos enlazados a los que se les vaya a nadiendo los nodos al principio. Se puede, en el ejemplo, ir a nadiendo entradas a una agenda personal (nombre, tel efono, email), seg un el usuario vaya introduciendo interactivamente los datos en el programa. Algo m as dif cil es a nadirlos al nal de todos ellos, ya para acceder al nal de ellos es necesario recorrer toda la cadena. Se deja as mismo como ejercicio el borrar un nodo seg un su posici on en la lista. Por ejemplo, borrar el nodo 5 o el 200. Tambi en debemos ver c omo se borrar a un nodo seg un el valor del dato que contenga.
1 2 nuevo lista 3

new

Figura 7: Inserci on en cabeza; son tres pasos: obtener la memoria, apuntar al antiguo primero y que el antiguo primero apunte al nuevo primero.

2.4.

Listas lineales de de nodos con simple y doble enlace, cabeceras

De la gesti on de memoria din amica mediante listas de nodos enlazados se deduce que la mayor dicultad de las estructuras de listas lineales es la de que el acceso a los nodos se hace linealmente m as y m as lento conforme aumenta la longitud de la lista. Es por eso que se desarrollan estructuras para tratar de evitar estos retrasos y tratar de igualar los tiempos de acceso a todos los nodos de la lista.

2.4

Listas lineales de de nodos con simple y doble enlace, cabeceras

new nuevo

lista tmp 2

Figura 8: Inserci on en mitad o nal; son cuatro pasos: obtener la memoria, avanzar un puntero al anterior al punto de inserci on, apuntar al siguiente y que el anterior apunte al nuevo.

lista aborrar 1

Figura 9: Borrar el primero; son tres pasos: guardar su direcci on, apuntar con la lista al siguiente (aqu hay que modicar la variable de la lista), liberar el que hay que borrar.

Las cabeceras son estructuras de datos que contienen informaci on relevante acerca de la cadena de nodos enlazados. Esta informaci on usualmente contiene el total de nodos existentes, quiz as la posici on del u ltimo nodo (para facilitar la operaci on de a nadido por la cola, que puede ser frecuente, seg un el caso). La cabecera siempre tiene que guardar la posici on del primer nodo de la cadena, pero adem as puede ser realmente u til guardar la posici on y el n umero del nodo (posici on en la cadena) del que se accedi o la u ltima vez: la u ltima visita. Y es que lo m as usual en estructuras de datos, especialmente en las lineales, en las cadenas de nodos din amicamente enlazados, es la operaci onde recorrido secuencial (por ejemplo, buscando, imprimiendo, etc.), de manera que si guardamos la posici on de la u ltima visita, cuando nos pidan la siguiente posici on el acceso ser a inmediato, de complejidad 1: mirar vel el siguiente nodo del de la u ltima visita y poner el valor de la u ltima visita en eese siguiente nodo antes de volver. El doble enlace mejora el acceso aleatorio a posiciones cualesquiera de la lista en la que ya se recuerde la posici on de la u ltima visita. Si guardamos la posici on y direcci on del u ltimo nodo visitado, s olo tendremos optimizado el acceso secuencial (hacia adelante) de los nodos de la lista. En estos casos se consigue tambi en una gran mejora si podemos acceder a los nodos no s olo hacia adelante sino tambi en a los nodos que est an antes que el nuestro, apuntando as no s olo a sigui sino tambi en a anterior.

2.5

Listas posicionales y ordenadas

lista tmp 1 2 aborrar

Figura 10: Borrar en medio o al nal; son cuatro pasos: avanzar hasta el anterior al que haya que borrar, guardar la direcci on del nodo a eliminar, hacer que el anterior apunte al siguiente, eliminar.

lista

Figura 11: Lista con una estructura de cabecera donde guardar metadatos importantes de la lista.

2.5.

Listas posicionales y ordenadas

El acceso a los nodos de las listas se suele hacer por la posici on, esto es, dada una posici on, desde 1 hasta la longitud o total de elementos de la lista, se devuelve el puntero o la informaci on contenida en ese nodo. Pero tambi en es muy importante el caso en el que se guardan los nodos ordenadamente. En este caso el acceso posterior puede ser mucho m as eciente. Sin embargo el costo inicial es mayor. As que depende de cu al vaya a ser la operaci on m as frecuente, la de a nadidos/borrados o la de b usqueda, porsterior, para que nos interese mantener a los nodos ordenados o no.

2.5

Listas posicionales y ordenadas

10

lista
nultvis

p ultima visita

Figura 12: Lista con dobles enlaces y una cabecera para aprovecharlos manteniendo recuerdo de la u ltima visita

Juan Falgueras Dpto. Lenguajes y Ciencias de la Computaci on Universidad de M alaga Despacho 3.2.32

También podría gustarte