Anda di halaman 1dari 10

Programacin Modular. ETSIT. 1o C.

o Apuntes del profesor Juan Falgueras Cano Curso 2005

2 Memoria dinmica a
Contenido
2. Memoria dinmica a 2.1. Gestin dinmica de la memoria . . . . . . . o a 2.1.1. Punteros . . . . . . . . . . . . . . . . . 2.2. Punteros en pseudocdigo . . . . . . . . . . . o 2.2.1. Utilidad de los punteros . . . . . . . . 2.2.2. Solicitud dinmica de memoria . . . . a 2.2.3. C++ . . . . . . . . . . . . . . . . . . . 2.2.4. Arrays dinmicos . . . . . . . . . . . . a 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 dinmica a
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 esttico. Qu signica esto? Que su ubicacin y capacidad de almacenamiento quedan a e o 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 fciles de localizar en la a memoria interna y tambin de simplicar y aligerar el cdigo de los programas ejecutables. e o Con los punteros las cosas no cambian: son estticos. Una variable de tipo puntero es una a variable esttica, pero. . . la memoria a la que apuntan puede que no. a Vamos a ver en este tema cmo se puede acceder directamente a la memoria interna libre del o sistema y cmo se pueden conseguir y devolver montones de memoria segn se necesiten a lo o u largo de la ejecucion de partes del programa.

2.1.

Gestin dinmica de la memoria o a

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

Un puntero es una variable esttica (como las vistas hasta ahora) pero destinada a 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 estticas siempre) puede guardar una direccin de memoria. Las cuestiones ahora son: QUE dia o reccin de memoria? , COMO obtener direcciones de la memoria interna?, PARA QUE guardar o esas direcciones?

2.1

Gestin dinmica de la memoria o a

a=33;

33

? 3 2 1 0

Figura 1: Hasta ahora no nos hemos preocupado de saber dnde el compilador ubicaba o las variables estticas que hemos usado. a

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; l mismo, el puntero es tambin una variable e e ms en memoria. a

Para responder a estas preguntas veremos primero un mtodo bsico para obtener direcciones e a de memoria; despus veremos un sistema de localizar bloques de memoria y nalmente, con esto e habrn quedado respondidas las tres preguntas. a No todos los lenguajes de programacin se arriesgan a permitir la gestin directa de la memoria o o mediante punteros1 . En C/C++: typedef int *TPuntEntero; // int* TPuntEntero; o /* variables */ int *punt; TPuntEntero a, b; es lo mismo

Una cuestin muy importante respecto a los punteros, y que algunos la toman como diferencia o entre mera direccin y puntero de verdad, es el hecho de que un puntero o siempre lleva asociado un tipo al que apuntar. Esto es, un puntero no es slo una direccin de memoria interna, para el compilador, tambin o o e 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 despus asignarle la direccin de un nmero real, por ejemplo. e o u En principio los valores de las direcciones, como las direcciones de cualquier gu de telfoa e nos, por ejemplo, son annimas y meras referencias geogrcas, en el caso del ordenador, meros o a nmeros posiciones de memoria. Sin embargo, el intercambio de contenidos entre dos direcciones, u por ejemplo, debe hacerse sabiendo que los contenidos de aqullas direcciones son compatibles, del e mismo tipo. Adems veremos en alguna ocasin la utilidad de poder hacer aritmtica sencilla con los a o e punteros: sumarle/restarle un nmero entero. Al estar el puntero ligado a un tipo concreto de u datos, este aumento/disminucin de la posicin se har correctamente: el valor del puntero a un o o a
1 decimos se arriesgan porque lenguajes ms modernos (como por ejemplo Java) evitan el uso de punteros a por muchos motivos, entre ellos, el de que el uso de punteros es bastante dif de proteger, de manera que y el cil programador puede fcilmente, con peque os errores destruir todo el estado de un sistema o acceder a cualquier a n parte protegida del mismo.

RAM

2.1

Gestin dinmica de la memoria o a

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

*(p+1) *p p

321 33 3FA04

3FA08 3FA04 3FA00

00000

Figura 3: Aritmtica de punteros. Si sumamos un entero a un puntero la nueva direccin e o tiene en cuenta el tama o del tipo de elemento apuntado. n

Una vez creada una variable o tipo puntero se inicializar, se le darn valores: a a 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 indicacin o de que no se apunta a nada. Aunque la direccin de memoria 0 existe, no se usa y en punteros o es la indicacin bsica de que no hay puntero. En los diagramas un puntero NULL se suele dibujar o a 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 direccin de memoria de cualquier elemento. o 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 direccin dnde el compilador ha situado a otra variable, tambin de tipo entero. o o e El tener guardada la direccin ser algo bastante intil si tan slo fuese eso, una direccin o a u o o que se guarda, la segunda parte es acceder a esa direccin. Esto es, nos interesa conocer qu es lo o e que hay en el sitio al que apunta un puntero. Si no tuviesemos esta posibilidad, tan slo habr o amos guardado la direccin para nada. o Para acceder a lo apuntado por una direccin, por un puntero, debemos desreferenciarlo o o indireccionarlo leer lo apuntado por l. Para acceder a lo apuntado por un puntero los lenguajes e de programacin disponen de un operador de desreferenciacin, poniendo el operador delante del o o puntero se entiende que lo que queremos es la informacin que hay en la direccin escrita en ese o o puntero. En C/C++, siguiendo el ejemplo anterior, *p = x;

2.2

Punteros en pseudocdigo o

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 ms sencilla como siempre a x = 3; El resultado es equivalente en estos casos.

2.2.

Punteros en pseudocdigo o

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 utilidad puede tener el anterior uso de las variables de tipo puntero? e Hasta ahora la introduccin de la direccin de las variables y zonas de memoria con los o o punteros no aade ninguna ventaja, excepto en el lenguaje C, dnde es el unico mecanismo posible n o para poder modicar parmetros: pasar una copia de la direccin a la funcin y en sta indireccionar a o o e y modicar el elemento pasado a travs de su direccin. e o Un uso fundamental es el que se da en los lenguajes en los que no existe un paso de parmetros a por referencia, como le ocurre, por ejemplo al lenguaje C, se recurre al siguiente mecanismo: a la funcin/procedimiento se le pasa una copia (siempre) de la direccin de la variable (&) y en la o o funcin se opera con lo apuntado por esa direccin (*): de esa forma se accede al contenido de la o o 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 estticas de los prograa mas no tiene ningn otro inters ya que para modicarlas, leerlas, etc, bastar como siempre con u e a usar su nombre, la etiqueta que los compiladores permiten crear para cada variable esttica: a x = 3; Sin embargo, la segunda forma de obtener direcciones de memoria es la ms interesante y la a que nos ocupar en este tema. a 2.2.2. Solicitud dinmica de memoria a

El principal uso de los punteros est en liberar la rigidez de los espacios de memoria para a datos. Las variables estticas no pueden crecer durante la ejecucin de un programa y, si nos hace a o falta ms espacio para guardar ms datos durante la ejecucin de un programa, necesitaremos a a o algo nuevo. Los lenguajes que utilizan punteros aportan funciones que localizan y reservan para nuestro programa tamaos solicitados de memoria interna. n Para ello los programas tienen una zona de memoria libre propia (aunque tambin en los e sistemas ms modernos esta memoria se pide a la del sistema, que adems es paginable y compara a tible con memoria virtual de disco) llamada arena situada entre la pila y los datos estticos. Ver a Figura 5 En C el mecanismo que se utiliza es el de decir la cantidad de memoria que se necesita a una funcin y sta devuelve una direccin, la direccin del comienzo del lugar dnde ha reservado ese o e o o o

2.2

Punteros en pseudocdigo o

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 dinmicamente durante su ejecucin. Otras zonas son las de datos inicialia o zados: constantes y cadenas de mensajes, etc. . . y la zona que durante la ejecucin y de manera esttica tienen reservadas las variables. La pila es la o a zona, dinmica tambin, que se usa para el paso de parmetros a las funciones. a e a

espacio de memoria2 . Para ello el lenguaje C aporta bsicamente dos operadores para la gestin dinmica de la a o a 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 nmero u de tipo entero, pedimos espacio para un nmero entero. sizeof(int) es una funcin interna de C u o que dice el espacio en bytes que ocupa cualquier tipo. Por otro lado la funcin malloc(..) slo o o requiere conocer ese dato, la cantidad de bytes que se necesitan. malloc busca espacio libre, los bloquea, y devuelve la direccin dnde lo ha encontrado: o o p = malloc(sizeof(int)); p = malloc(10000); reservar 10.000 bytes de memoria que comenzar en p. a an Si malloc no encuentra ms memoria libre devuelve una direccin nula: nul o NIL 0. a o o Una vez que hayamos operado con la direccin (con el contenido al que apunta esa direccin, o o en realidad), debemos liberarla. La liberacin de los bloques pedidos al sistema es esencial si el o 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 tctica a muy sana el liberar los bloques de memoria que ya dejan de ser necesarios. La funcin en C para o realizar esto es free. free slo requiere conocer el lugar dnde comienza el bloque antes prestado. o o Curiosamente no requiere que se le indique cunto ocupaba aquella reserva: a free(p); vuelve a dejar ese sitio disponible para futuras necesidades de meroria. 2.2.3. C++

C++ utiliza un operadores ms complejos new que deduce el tamao necesitado y modica a n el parmetro puntero que se le pone. y delete para liberar esta memoria. a Estos operadores son los mismos que se utilizan para crear objetos dinmicamente y permiten a adems la inicializacin de los elementos: Por ejemplo a o
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 slo para qu tipo de dato queremos apuntar, y el compilador o e deduce el espacio de memoria que se necesita y asigna esa direccin al puntero. o 3 Para ver las dems funciones que aporta el sistema, ver la cabecera: <stdlib.h>. a

2.3

Estructuras de datos recursivas

int *pint, *parr; pint = new int; parr = new int[10] // ... delete pint; delete[] parr; Ntese que en C++ la solicitud es siempre por unidades lgicas: se pide un entero, una cha, un o o carcter; o bien se pide un array de n elementos. En el ultimo caso se tiene que liberar con la a sintaxis especial delete[] loquesea; En C++ el operador new puede llevar despus del tipo que crea un valor para inicializarlo: e double *p; p = new double (3.1416); 2.2.4. Arrays dinmicos a

Una inmediata ventaja de la creacin de elementos dinmicamente es la posibilidad de crear o a arrays del tamao que nos haga falta durante la ejecucin del programa: n o int n; cout << "introducir nmero de elementos: "; u 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 ms estas estructuras debemos a liberarlas: delete[] datos;

2.3.

Estructuras de datos recursivas

Aunque se pueda localizar un bloque de memoria, el problema es que no se sabe cuntos de a esos bloques de memoria necesitaremos. Si un programa requiere diez bloques de memoria en una ejecucin habr que tener previstas o a diez variables de tipo puntero para guardar las direcciones de estos bloques. Pero si durante la ejecucin se hubiesen requerido veinte, tendr o amos un problema. La solucin est en a enganchar un bloque con el siguiente (nuevamente mediante punteros) y o a formar as cadenas de bloques todos ligados y acceder a ellos mediante una direccin, un puntero, o 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 ms bien) o un objeto cualquiera en a funcin de otro an no denido. Esto es necesario as por la propia recursividad de la estructura. o u Un enlace es un puntero a algo que tiene un enlace a lo que estamos an deniendo. u

Lista

lista

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

La cuestin ahora es que cada vez que se pide un bloque para un nodo tambin se est gao e a nando espacio para guardar la direccin del siguiente nodo o bloque. De esta forma, de un slo o o 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 ms o menos complejas pero que permitirn acceder a los nodos segn nos interese. a a u Cuando se acaba la secuencia de nodos y ya no se tienen ms nodos detrs, se utiliza como a a indicador de siguiente el valor de puntero nulo. De otra manera, el ultimo no tiene a nadie detrs a y su siguiente es NULL. Se deja como ejercicio la construccin de una estructura lineal o lista de nodos enlazados o a los que se les vaya aadiendo los nodos al principio. Se puede, en el ejemplo, ir aadiendo n n entradas a una agenda personal (nombre, telfono, email), segn el usuario vaya introduciendo e u interactivamente los datos en el programa. Algo ms dif es aadirlos al nal de todos ellos, ya para acceder al nal de ellos es necesario a cil n recorrer toda la cadena. Se deja as mismo como ejercicio el borrar un nodo segn su posicin en la lista. Por ejemplo, u o borrar el nodo 5 el 200. Tambin debemos ver cmo se borrar un nodo segn el valor del dato o e o a u que contenga.
1 2 nuevo lista 3

new

Figura 7: Insercin en cabeza; son tres pasos: obtener la memoria, apuntar al antiguo o 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 gestin de memoria dinmica mediante listas de nodos enlazados se deduce que la mayor o a dicultad de las estructuras de listas lineales es la de que el acceso a los nodos se hace linealmente ms y ms lento conforme aumenta la longitud de la lista. Es por eso que se desarrollan estructuras a a 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: Insercin en mitad o nal; son cuatro pasos: obtener la memoria, avanzar o un puntero al anterior al punto de insercin, apuntar al siguiente y que el o anterior apunte al nuevo.

lista aborrar 1

Figura 9: Borrar el primero; son tres pasos: guardar su direccin, apuntar con la o 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 informacin relevante acerca de la cadena o de nodos enlazados. Esta informacin usualmente contiene el total de nodos existentes, quizs la o a posicin del ultimo nodo (para facilitar la operacin de aadido por la cola, que puede ser frecuente, o o n segn el caso). u La cabecera siempre tiene que guardar la posicin del primer nodo de la cadena, pero adems o a puede ser realmente util guardar la posicin y el nmero del nodo (posicin en la cadena) del o u o que se accedi la ultima vez: la ultima visita. Y es que lo ms usual en estructuras de datos, o a especialmente en las lineales, en las cadenas de nodos dinmicamente enlazados, es la operacinde a o recorrido secuencial (por ejemplo, buscando, imprimiendo, etc.), de manera que si guardamos la posicin de la ultima visita, cuando nos pidan la siguiente posicin el acceso ser inmediato, de o o a complejidad 1: mirar vel el siguiente nodo del de la ultima visita y poner el valor de la ultima 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 posicin de la ultima visita. Si guardamos la posicin y direccin del ultimo nodo o o o visitado, slo tendremos optimizado el acceso secuencial (hacia adelante) de los nodos de la lista. o En estos casos se consigue tambin una gran mejora si podemos acceder a los nodos no slo hacia e o adelante sino tambin a los nodos que estn antes que el nuestro, apuntando as no slo a sigui e a o sino tambin a anterior. e

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 direccin del nodo a eliminar, hacer que o 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 posicin, esto es, dada una posicin, o o desde 1 hasta la longitud o total de elementos de la lista, se devuelve el puntero o la informacin o contenida en ese nodo. Pero tambin es muy importante el caso en el que se guardan los nodos e ordenadamente. En este caso el acceso posterior puede ser mucho ms eciente. Sin embargo a el costo inicial es mayor. As que depende de cul vaya a ser la operacin ms frecuente, la a o a de aadidos/borrados o la de bsqueda, porsterior, para que nos interese mantener a los nodos n u 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 ultima visita

Juan Falgueras Dpto. Lenguajes y Ciencias de la Computacin o Universidad de Mlaga a Despacho 3.2.32

Anda mungkin juga menyukai