Programaci´n Modular. ETSIT. 1o C.

o Apuntes del profesor Juan Falgueras Cano Curso 2005

2 Memoria din´mica a
Contenido
2. Memoria din´mica a 2.1. Gesti´n din´mica de la memoria . . . . . . . o a 2.1.1. Punteros . . . . . . . . . . . . . . . . . 2.2. Punteros en pseudoc´digo . . . . . . . . . . . o 2.2.1. Utilidad de los punteros . . . . . . . . 2.2.2. Solicitud din´mica de memoria . . . . a 2.2.3. C++ . . . . . . . . . . . . . . . . . . . 2.2.4. Arrays din´micos . . . . . . . . . . . . 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 din´mica a
Hasta ahora todos los tipos de datos predefinidos (enteros, reales, caracteres, booleanos) y definidos por el usuario (enumerados, y estructuras de datos arrays y registros) han sido de tipo est´tico. ¿Qu´ significa esto? Que su ubicaci´n y capacidad de almacenamiento quedan a e o r´ ıgidamente definidos 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´ciles de localizar en la a memoria interna y tambi´n de simplificar y aligerar el c´digo de los programas ejecutables. e o Con los punteros las cosas no cambian: son est´ticos. Una variable de tipo puntero es una a variable est´tica, pero. . . la memoria a la que apuntan puede que no. a Vamos a ver en este tema c´mo se puede acceder directamente a la memoria interna libre del o sistema y c´mo se pueden conseguir y devolver montones de memoria seg´n se necesiten a lo o u largo de la ejecucion de partes del programa.

2.1.

Gesti´n din´mica de la memoria o a

Las variables en los programas se declaran mediante un tipo (modo de codificaci´n) y un o nombre de variable. As´ para nosotros una variable es un identicador que se refiere a un espacio ı reservado de una manera definitiva (est´ticamente) por el compilador en alg´n lugar (que hasta a u ahora no nos ha interesado). 2.1.1. Punteros

Un puntero es una variable est´tica (como las vistas hasta ahora) pero destinada a a guardar direcciones de memoria de otras varibles. Lo que esto significa 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 ´ est´ticas siempre) puede guardar una direcci´n de memoria. Las cuestiones ahora son: ¿QUE dia o ´ ´ recci´n de memoria? , ¿COMO obtener direcciones de la memoria interna?, ¿PARA QUE guardar o esas direcciones?

o /* variables */ int *punt. el puntero es tambi´n una variable e e m´s en memoria. e o u En principio los valores de las direcciones. son an´nimas y meras referencias geogr´ficas.1 Gesti´n din´mica de la memoria o a 2 a=33. Adem´s veremos en alguna ocasi´n la utilidad de poder hacer aritm´tica sencilla con los a o e punteros: sumarle/restarle un n´mero entero. ´l mismo. es lo mismo Una cuesti´n muy importante respecto a los punteros. en el sentido de que si declaramos un puntero a entero. 33 ? 3 2 1 0 Figura 1: Hasta ahora no nos hemos preocupado de saber d´nde el compilador ubicaba o las variables est´ticas que hemos usado. En C/C++: typedef int *TPuntEntero. despu´s veremos un sistema de localizar bloques de memoria y finalmente. Esto va a ser fundamental para ayudar al compilador a disciplinar nuestro programa. tambi´n o o e asociamos con esas direcciones un tipo de datos. RAM . por ejemplo. u por ejemplo. b. Al estar el puntero ligado a un tipo concreto de u datos. es el hecho de que un puntero o siempre lleva asociado un tipo al que apuntar. entre ellos. a Para responder a estas preguntas veremos primero un m´todo b´sico para obtener direcciones e a de memoria. con esto e habr´n quedado respondidas las tres preguntas. del e mismo tipo. debe hacerse sabiendo que los contenidos de aqu´llas direcciones son compatibles. el de que el uso de punteros es bastante dif´ de proteger.2. a No todos los lenguajes de programaci´n se arriesgan a permitir la gesti´n directa de la memoria o o mediante punteros1 . un puntero no es s´lo una direcci´n de memoria interna. meros o a n´meros posiciones de memoria. este aumento/disminuci´n de la posici´n se har´ correctamente: el valor del puntero a un o o a 1 decimos “se arriesgan” porque lenguajes m´s modernos (como por ejemplo Java) evitan el uso de punteros a por muchos motivos. con peque˜ os errores destruir todo el estado de un sistema o acceder a cualquier a n parte protegida del mismo. Esto es. y que algunos la toman como diferencia o entre mera direcci´n y puntero de verdad. no podemos despu´s asignarle la direcci´n de un n´mero real. el intercambio de contenidos entre dos direcciones. a 3FA08 *p *p 33 33 3FA04 3FA00 p 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. de manera que y el ıcil programador puede f´cilmente. TPuntEntero a. // ´ int* TPuntEntero. en el caso del ordenador. Sin embargo. como las direcciones de cualquier gu´ de tel´foıa e nos. para el compilador. por ejemplo.

poniendo el operador delante del o o puntero se entiende que lo que queremos es la informaci´n que hay en la direcci´n escrita en ese o o puntero. le podemos asignar la direcci´n d´nde el compilador ha situado a otra variable. *(p+1) *p p 321 33 3FA04 3FA08 3FA04 3FA00 00000 Figura 3: Aritm´tica de punteros. El operador & es un potente operador de C/C++ que sirve para obtener la direcci´n de memoria de cualquier elemento. Para acceder a lo apuntado por un puntero los lenguajes e de programaci´n disponen de un operador de desreferenciaci´n. no apunta a nada. una direcci´n o ıa u o o que se guarda. tambi´n de tipo entero. Ver Figura 4 p 0 Figura 4: Puntero NULL: enlace a tierra. se le dar´n valores: a a punt = 0. Un valor muy especial en los punteros es el valor 0 (NULL). *p = x. o Por ejemplo. El valor 0 en los punteros es la indicaci´n o de que no se apunta a nada. Si no tuviesemos esta posibilidad. o o e El tener guardada la direcci´n ser´ algo bastante in´til si tan s´lo fuese eso. que es un puntero a entero. nos interesa conocer qu´ es lo o e que hay en el sitio al que apunta un puntero. Esto es. la segunda parte es acceder a esa direcci´n. Hemos usado en el ejemplo anterior el operador &. punt = &ficha. debemos desreferenciarlo o o indireccionarlo leer lo apuntado por ´l. siguiendo el ejemplo anterior. en C/C++: int x.1 Gesti´n din´mica de la memoria o a 3 entero aumentado en uno se incrementar´ en realidad en el tama˜o de un entero apuntando al a n siguiente entero (ver Fig. punt = &i. n Una vez creada una variable o tipo puntero se inicializar´. En C/C++. . 3). significa que a la vez que creamos la variable puntero p. por un puntero. En los diagramas un puntero NULL se suele dibujar o a como un enlace a tierra de electricidad. Aunque la direcci´n de memoria 0 existe. no se usa y en punteros o es la indicaci´n b´sica de que no hay puntero. Si sumamos un entero a un puntero la nueva direcci´n e o tiene en cuenta el tama˜ o del tipo de elemento apuntado. o Para acceder a lo apuntado por una direcci´n.2. tan s´lo habr´ o ıamos guardado la direcci´n para nada. int *p = &x.

Este mecanismo ya es conocido por nosotros. etc. bastar´ como siempre con u e a usar su nombre. la direcci´n del comienzo del lugar d´nde ha reservado ese o e o o o .2. sin embargo no los arrays. // array de 10 punteros (raro) NOTA: en pseudolenguaje. si nos hace a o falta m´s espacio para guardar m´s datos durante la ejecuci´n de un programa. a 2. //tipo puntero N array_natural[1. por ejemplo al lenguaje C. necesitaremos a a o algo nuevo. // N *arrPunt[1.2 Punteros en pseudoc´digo o 4 copia el contenido de la variable x al lugar (de tipo entero) al que apunta la variable puentero p. como le ocurre. leerlas. Solicitud din´mica de memoria a El principal uso de los punteros est´ en liberar la rigidez de los espacios de memoria para a datos. excepto en el lenguaje C. Ejemplos: N *puntero_a_natural.1. n Para ello los programas tienen una zona de memoria libre propia (aunque tambi´n en los e sistemas m´s modernos esta memoria se pide a la del sistema.20].2.2. Aparte de este modo de uso el conocer las direcciones de las variables est´ticas de los prograa mas no tiene ning´n otro inter´s ya que para modificarlas. que adem´s es paginable y compara a tible con memoria virtual de disco) llamada arena situada entre la pila y los datos est´ticos.10]. Ver a Figura 5 En C el mecanismo que se utiliza es el de decir la cantidad de memoria que se necesita a una funci´n y ´sta devuelve una direcci´n.2.2. se recurre al siguiente mecanismo: a la funci´n/procedimiento se le pasa una copia (siempre) de la direcci´n de la variable (&) y en la o o funci´n se opera con lo apuntado por esa direcci´n (*): de esa forma se accede al contenido de la o o variable que se haya pasado. *LINK. el contenido de las estructuras se puede copiar. Utilidad de los punteros ¿Qu´ utilidad puede tener el anterior uso de las variables de tipo puntero? e Hasta ahora la introducci´n de la direcci´n de las variables y zonas de memoria con los o o punteros no a˜ade ninguna ventaja. d´nde es el unico mecanismo posible n o ´ para poder modificar par´metros: pasar una copia de la direcci´n a la funci´n y en ´sta indireccionar a o o e y modificar el elemento pasado a trav´s de su direcci´n. // NODO *ENLACE. //array de 20 naturales enteros 2. Naturalmente podemos escribir el contenido de la variable x de la forma m´s sencilla como siempre a x = 3. Sin embargo... Los lenguajes que utilizan punteros aportan funciones que localizan y reservan para nuestro programa tama˜os solicitados de memoria interna. 2. El resultado es equivalente en estos casos. la segunda forma de obtener direcciones de memoria es la m´s interesante y la a que nos ocupar´ en este tema. la etiqueta que los compiladores permiten crear para cada variable est´tica: a x = 3. Punteros en pseudoc´digo o Puntero <TipoApuntado> *PTI. Las variables est´ticas no pueden crecer durante la ejecuci´n de un programa y. e o Un uso fundamental es el que se da en los lenguajes en los que no existe un paso de par´metros a por referencia.

. . Por ejemplo. o 3 Para ver las dem´s funciones que aporta el sistema. Pero en general es una t´ctica a muy sana el liberar los bloques de memoria que ya dejan de ser necesarios.h>. ver la cabecera: <stdlib. a e a espacio de memoria2 . que sirve para ser usada y reusada din´micamente durante su ejecuci´n. y “delete” para liberar esta memoria. y devuelve la direcci´n d´nde lo ha encontrado: o o p = malloc(sizeof(int)). si queremos que el sistema localice espacio para un n´mero u de tipo entero. vuelve a dejar ese sitio disponible para futuras necesidades de meroria. en muchos lenguajes no es necesario decir la cantidad de memoria que queremos reservar sino s´lo para qu´ tipo de dato queremos apuntar. Otras zonas son las de datos inicialia o zados: constantes y cadenas de mensajes. y el compilador o e deduce el espacio de memoria que se necesita y asigna esa direcci´n al puntero. La primera es la que hace las reservas. pedimos espacio para un n´mero entero. La liberaci´n de los bloques pedidos al sistema es esencial si el o programa ha de seguir iterando y pidiendo en otros sitios nuevos bloques. que se usa para el paso de par´metros a las funciones.) s´lo o o requiere conocer ese dato.000 bytes de memoria que comenzar´ en p. a o o Una vez que hayamos operado con la direcci´n (con el contenido al que apunta esa direcci´n. free s´lo requiere conocer el lugar d´nde comienza el bloque antes prestado. No siempre es as´ y el ı sistema usualmente libera todo lo prestado al programa al terminar. Por otro lado la funci´n malloc(. etc.2 Punteros en pseudoc´digo o 5 código PROGRAMA datos inicializados datos no inicializados arena pila de programa Figura 5: Zona de memoria “libre” de un programa. Para ello el lenguaje C aporta b´sicamente dos operadores para la gesti´n din´mica de la a o a memoria: malloc y free. La pila es la o a zona. p = malloc(10000). y la zona que durante la ejecuci´n y de manera est´tica tienen reservadas las variables. la cantidad de bytes que se necesitan. malloc busca espacio libre. a Estos operadores son los mismos que se utilizan para crear objetos din´micamente y permiten a adem´s la inicializaci´n de los elementos: Por ejemplo a o 2 dado que los punteros llevan asociados el tipo de datos al que apuntan. o o en realidad).2. o o Curiosamente no requiere que se le indique cu´nto ocupaba aquella reserva: a free(p). los bloquea. ıa ıan Si malloc no encuentra m´s memoria libre devuelve una direcci´n nula: nul o NIL ´ 0.3. C++ C++ utiliza un operadores m´s complejos “new” que deduce el tama˜o necesitado y modifica a n el par´metro puntero que se le pone. debemos liberarla. din´mica tambi´n. La funci´n en C para o realizar esto es free. reservar´ 10. sizeof(int) es una funci´n interna de C u o que dice el espacio en bytes que ocupa cualquier tipo. las devuelve al sistema3 .2. 2. la segunda es la que las libera. a . .

u cin >> n. 2. TNodo *sigui. delete[] parr. typedef TNodo *TEnlace. Para poder formar estas cadenas de bloques lo que se hace es definir una estructura de datos como (en C++) (ver Fig 6): struct TNodo { int dato. una ficha. TEnlace sigui.4.. cuando sepamos que no se van a utilizar m´s estas estructuras debemos a liberarlas: delete[] datos. delete pint. }. para facilitar su uso. cout << "introducir n´mero de elementos: ". En el ultimo caso se tiene que liberar con la a ´ sintaxis especial delete[] loquesea. Estructuras de datos recursivas Aunque se pueda localizar un bloque de memoria. *parr.1416). // quedan inicializados a 0 for (int i=0. o bien se pide un array de n elementos.2. En C++ el operador new puede llevar despu´s del tipo que crea un valor para inicializarlo: e double *p. un puntero. pint = new int. Pero si durante la ejecuci´n se hubiesen requerido veinte. tendr´ o ıamos un problema. struct TNodo { int dato. La soluci´n 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 direcci´n. i<n.3 Estructuras de datos recursivas 6 int *pint. double *datos.2. ". el problema es que no se sabe cu´ntos de a esos bloques de memoria necesitaremos. un o o car´cter. En todos los casos. o. cout << endl. . p = new double (3. ı o el puntero al primer bloque.i++) cout << datos[i] << ".. N´tese que en C++ la solicitud es siempre por unidades l´gicas: se pide un entero. Si un programa requiere diez bloques de memoria en una ejecuci´n habr´ que tener previstas o ıa diez variables de tipo puntero para guardar las direcciones de estos bloques.3. datos = new double[n]. Arrays din´micos a Una inmediata ventaja de la creaci´n de elementos din´micamente es la posibilidad de crear o a arrays del tama˜o que nos haga falta durante la ejecuci´n del programa: n o int n. 2. parr = new int[10] // . }.

Algo m´s dif´ es a˜adirlos al final de todos ellos. 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. 1 2 nuevo lista 3 new Figura 7: Inserci´n en cabeza. . apuntar al antiguo o primero y que el antiguo primero apunte al nuevo primero. ya para acceder al final de ellos es necesario a ıcil n recorrer toda la cadena. tel´fono. Se deja as´ ımismo como ejercicio el borrar un nodo seg´n su posici´n en la lista. cabeceras 7 Es la primera vez que estamos definiendo una variable (un tipo m´s bien) o un objeto cualquiera en a funci´n de otro a´n no definido. Dependiendo del criterio utilizado para enganchar esos nodos tendremos estructuras lineales o no lineales m´s o menos complejas pero que permitir´n acceder a los nodos seg´n nos interese. e La cuesti´n ahora es que cada vez que se pide un bloque para un nodo tambi´n se est´ gao e a nando espacio para guardar la direcci´n del siguiente nodo o bloque. o u ı Un enlace es un puntero a algo que tiene un enlace a lo que estamos a´n definiendo. etc´tera. el ultimo no tiene a nadie detr´s ´ a y su siguiente es NULL. 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. a a u Cuando se acaba la secuencia de nodos y ya no se tienen m´s nodos detr´s. Esto es necesario as´ por la propia recursividad de la estructura. De esta forma. seg´n el usuario vaya introduciendo e u interactivamente los datos en el programa. email). 2. se utiliza como a a indicador de siguiente el valor de puntero nulo. cabeceras De la gesti´n de memoria din´mica mediante listas de nodos enlazados se deduce que la mayor o a dificultad de las estructuras de listas lineales es la de que el acceso a los nodos se hace linealmente m´s y m´s lento conforme aumenta la longitud de la lista. Listas lineales de de nodos con simple y doble enlace.4. en el ejemplo. Tambi´n debemos ver c´mo se borrar´ un nodo seg´n el valor del dato o e o ıa u que contenga. ir a˜adiendo n n entradas a una agenda personal (nombre. De otra manera. son tres pasos: obtener la memoria. Por ejemplo. u o borrar el nodo 5 ´ el 200.4 Listas lineales de de nodos con simple y doble enlace. Se puede.2. de un s´lo o o puntero podemos colgar la cantidad de nodos que nos vayan haciendo falta durante el programa. Se deja como ejercicio la construcci´n de una estructura lineal o lista de nodos enlazados o a los que se les vaya a˜adiendo los nodos al principio.

Y es que lo m´s usual en estructuras de datos. Si guardamos la posici´n y direcci´n del ultimo nodo o ´ o o ´ visitado. buscando. u La cabecera siempre tiene que guardar la posici´n del primer nodo de la cadena. 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. imprimiendo. son tres pasos: guardar su direcci´n. 2 3 lista aborrar 1 Figura 9: Borrar el primero. apuntando as´ no s´lo a “sigui” e a ı o sino tambi´n a “anterior”. cabeceras 8 new nuevo 1 4 3 lista tmp 2 Figura 8: Inserci´n en mitad o final. de manera que si guardamos la posici´n de la ultima visita. o ´ ´ a especialmente en las lineales. son cuatro pasos: obtener la memoria. cuando nos pidan la siguiente posici´n el acceso ser´ inmediato. s´lo tendremos optimizado el acceso secuencial (hacia adelante) de los nodos de la lista. quiz´s la o a posici´n del ultimo nodo (para facilitar la operaci´n de a˜adido por la cola. apuntar al siguiente y que el o anterior apunte al nuevo. o ´ o n seg´n el caso). en las cadenas de nodos din´micamente enlazados. avanzar o un puntero al anterior al punto de inserci´n.4 Listas lineales de de nodos con simple y doble enlace. e . que puede ser frecuente. El doble enlace mejora el acceso aleatorio a posiciones cualesquiera de la lista en la que ya se recuerde la posici´n de la ultima visita. Esta informaci´n usualmente contiene el total de nodos existentes. o En estos casos se consigue tambi´n una gran mejora si podemos acceder a los nodos no s´lo hacia e o adelante sino tambi´n a los nodos que est´n antes que el nuestro.).2. pero adem´s o a puede ser realmente util guardar la posici´n y el n´mero del nodo (posici´n en la cadena) del ´ o u o que se accedi´ la ultima vez: la ultima visita. etc. liberar el que ı hay que borrar. es la operaci´nde a o recorrido secuencial (por ejemplo. Las cabeceras son estructuras de datos que contienen informaci´n relevante acerca de la cadena o de nodos enlazados. apuntar con la o lista al siguiente (aqu´ hay que modificar la variable de la lista).

. eliminar. o o desde 1 hasta la longitud o total de elementos de la lista. 2.5. En este caso el acceso posterior puede ser mucho m´s eficiente. la ı a o a de a˜adidos/borrados o la de b´squeda. dada una posici´n. Pero tambi´n es muy importante el caso en el que se guardan los nodos e ordenadamente. As´ que depende de cu´l vaya a ser la operaci´n m´s frecuente. hacer que o el anterior apunte al siguiente. guardar la direcci´n del nodo a eliminar. para que nos interese mantener a los nodos n u ordenados o no. porsterior. Sin embargo a el costo inicial es mayor. se devuelve el puntero o la informaci´n o contenida en ese nodo. esto es. son cuatro pasos: avanzar hasta el anterior al que haya que borrar. Listas posicionales y ordenadas El acceso a los nodos de las listas se suele hacer por la posici´n.5 Listas posicionales y ordenadas 9 3 lista tmp 1 2 aborrar 4 Figura 10: Borrar en medio o al final.2. lista Figura 11: Lista con una estructura de cabecera donde guardar metadatos importantes de la lista.

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.32 .2. Lenguajes y Ciencias de la Computaci´n o Universidad de M´laga a Despacho 3.2.

Sign up to vote on this title
UsefulNot useful