16
2013 Autor: Bianca González Editorial: Punteros-C 04/02/2013 Memoria Dinámica

Memoria dinámica revista

Embed Size (px)

DESCRIPTION

revista de memoria dinámica para la materia de estructura de datos 1

Citation preview

Page 1: Memoria dinámica revista

0

2013

Autor: Bianca González

Editorial: Punteros-C

04/02/2013

Memoria Dinámica

Page 2: Memoria dinámica revista

1

Definición de

Memoria Dinámica

Diferencias y

Ventajas,

Desventajas

Puntero

Variable Puntero

Declaración de

Variable Puntero en

lenguaje C

Funciones de la

memoria dinámica

¿Sabías que…?

Horóscopo General

para el Programador

Pág. 2

Pág. 4

Pág. 2-3

2-3

Pág. 4-5

Un puntero tiene

Su propia

dirección de

Memoria:

&punt &car

Pág. 5

Pág. 5-8

Pág. 9-12

Pág. 13-14

Page 3: Memoria dinámica revista

2

Se refiere a

aquella memoria que no puede ser

definida ya que no se conoce o no se

tiene idea del número de la variable

a considerarse, la solución a este

problema es la memoria dinámica

que permite solicitar memoria

en tiempo de ejecución, por lo que

cuanta más memoria se necesite,

más se solicita al sistema operativo.

El sistema operativo maneja la

memoria gracias al uso de punteros,

por la misma naturaleza del proceso

nos impide conocer el tamaño de la

memoria necesaria en el momento

descompilar.

En lenguaje C la memoria

dinámica puede ser definida como

aquella memoria que se reserva en

tiempo de ejecución. Su principal

ventaja frente a la estática, es que

su tamaño puede variar durante la

ejecución del programa.(En C, el

programador es encargado de

liberar esta memoria cuando no la

utilice más).

El uso de memoria dinámica es

necesario cuando a priori no

conocemos el número de

datos/elementos a tratar; sin

embargo es algo más lento, pues es

en tiempo de ejecución cuando se

determina la memoria a usar. En

contrapartida la memoria estática

es más rápida ya que está disponible

desde que se inicio el programa.

Diferencias y Ventajas

La memoria reservada de

forma dinámica suele estar

alojada en el heap o

almacenamiento libre, y la

memoria estática en el stack

o pila (con excepción de los

objetos de duración estática,

que se verán más adelante,

los cuales normalmente se

colocan en una zona estática

de datos).

La pila generalmente es una

zona muy limitada. El heap, en

cambio, en

principio podría estar

limitado por la cantidad de

memoria disponible durante la

ejecución del programa y el

máximo de memoria que el

sistema operativo permita

direccionar a un proceso. La

pila puede crecer de forma

dinámica, pero esto depende

del sistema operativo. En

Page 4: Memoria dinámica revista

3

cualquier caso, lo único que se

puede asumir es que muy

probablemente dispondremos

de menor espacio en la pila

que en el heap.

Otra ventaja de la memoria

dinámica es que se puede ir

incrementando durante la

ejecución del programa. Esto

permite, por ejemplo,

trabajar con arreglos

dinámicos. Aunque en C, a

partir del estándar C99 se

permite la creación de

arreglos cuyo tamaño se

determina en tiempo de

ejecución, no todos los

compiladores implementan

este estándar. Además, se

sigue teniendo la limitante de

que su tamaño no puede

cambiar una vez que se

especifica, cosa que sí se

puede lograr asignando

memoria de forma dinámica.

Desventajas

Una desventaja de la memoria

dinámica es que es más difícil

de manejar. La memoria

estática tiene una duración

fija, que se reserva y libera

de forma automática. En

contraste, la memoria

dinámica se reserva de forma

explícita y continúa

existiendo hasta que sea

liberada, generalmente por

parte del programador.

La memoria dinámica puede

afectar el rendimiento.

Puesto que con la memoria

estática el tamaño de las

variables se conoce en tiempo

de compilación, esta

información está incluida en

el código objeto generado,

por lo cual el proceso es muy

eficiente.

Cuando se reserva memoria

de manera dinámica, se tienen

que llevar a cabo varias

tareas, como buscar un

bloque de memoria libre y

almacenar la posición y

tamaño de la memoria

asignada, de manera que

pueda ser liberada más

adelante. Todo esto

representa una carga

Page 5: Memoria dinámica revista

4

adicional, aunque esto

depende de la implementación

y hay técnicas para reducir su

impacto.

Puntero

Un puntero es una variable

que contiene la dirección de

memoria donde se encuentra

almacenado un dato.

También se podría decir que

es un objeto que apunta a otro

objeto. Es decir, una variable cuyo

valor es la dirección de memoria de

otra variable.

No hay que confundir una

dirección de memoria con el

contenido de esa dirección de

memoria.

La dirección de la variable x

(&x) es 1502

El contenido de la variable x

es 25

En C no debemos, ni podemos,

indicar numéricamente la dirección

de memoria, si no que utilizamos una

etiqueta que conocemos como

variable (en su día definimos las

variables como direcciones de

memoria). Lo que nos interesa es

almacenar un dato, y no la

localización exacta de ese dato en

memoria.

En lenguaje c Los punteros en el Lenguaje

C, son variables que " apuntan ", es

decir que poseen la dirección de las

ubicaciones en memoria de otras

variables, y por medio de ellos

tendremos un poderoso método de

acceso a todas ellas.

Quizás este punto es el más

conflictivo del lenguaje, ya que

muchos programadores en otros

idiomas, y novatos en C, lo ven como

un método extraño ó al menos

desacostumbrado, lo que produce un

cierto rechazo. Sin embargo , y en

la medida que uno se va

familiarizando con ellos, se

convierten en la herramienta más

cómoda y directa para el manejo de

variables complejas, argumentos,

parámetros, etc, y se empieza a

preguntar cómo es que hizo para

programar hasta aquí, sin ellos . La

respuesta es que no lo ha hecho, ya

que los hemos usado en forma

encubierta, sin decir lo que eran.

Una variable Puntero

Es el dato cuya posición en

memoria está contenida en un

determinado puntero (variable

dinámica).

Page 6: Memoria dinámica revista

5

Una variable puntero se

declara como todas las

variables.

Debe ser del mismo tipo que

la variable apuntada. Su

identificador va precedido de

un asterisco (*):

int *punt;

Es una variable puntero que apunta a

variable que contiene un dato de

tipo entero llamada punt.

char *car: Es un puntero a variable de tipo

carácter.

long float *num;

float *mat[5]; // . . .

Es decir: hay tantos tipos de

punteros como tipos de datos,

aunque también pueden declararse

punteros a estructuras más

complejas (funciones, struct,

ficheros...) e incluso punteros vacíos

(void ) y punteros nulos (NULL).

Declaración de variables puntero:

Sea un fragmento de

programa en C:

Hay que tener cuidado con las

direcciones apuntadas:

*(punt + 1) repesenta el valor

contenida en la dirección de

memoria aumentada en una posición

(int=2bytes), que será un valor no

deseado. Sin embargo var+1

representa el valor 15.

punt + 1 representa lo mismo que

&var + 1 (avance en la dirección de

memoria de var).

Funciones de la Memoria

Dinámica:

El tratamiento de memoria

dinámica sigue tres pasos

fundamentales:

Un puntero tiene

Su propia

dirección de

Memoria:

&punt &car

Page 7: Memoria dinámica revista

6

1) Petición de memoria

(función malloc).

2) Utilización de dicha

memoria para nuestro

propósito.

3) Liberación de memoria

(función free).

malloc

La función malloc reserva un

bloque de memoria y devuelve un

puntero void al inicio de la misma.

Tiene la siguiente definición:

void *malloc(size_t size);

Donde el parámetro size especifica

el número de bytes a reservar. En

caso de que no se pueda realizar la

asignación, devuelve el valor nulo

(definido en la macro NULL), lo que

permite saber si hubo errores en la

asignación de memoria.

Ejemplo:

int *puntero;

char *puntcarc;

puntero=(int *)malloc(4);

puntcarc=(char *)malloc(200);

Uno de los usos más comunes

de la memoria dinámica es la

creación de vectores cuyo número

de elementos se define en tiempo

de ejecución:

int *vect1, n;

printf("N£mero de elementos del

vector: ");

scanf("%d", &n);

/* Reservar memoria para almacenar n enteros */

vect1 = malloc(n * sizeof(int));

/* Verificamos que la asignación se haya realizado correctamente */

if (vect1 == NULL) {

/* Error al intentar reservar memoria */

}

calloc

La función calloc funciona de

modo similar a malloc, pero además

de reservar memoria, inicializa a 0

la memoria reservada. Se usa

comúnmente para arreglos y

matrices. Está definida de esta

forma:

void *calloc(size_t nmemb, size_t

size);

El parámetro nmemb indica el

número de elementos a reservar,

y size el tamaño de cada elemento.

Page 8: Memoria dinámica revista

7

El ejemplo anterior se podría

reescribir con calloc de esta forma:

int *vect1, n;

printf("N£mero de elementos del

vector: ");

scanf("%d", &n);

/* Reservar memoria para almacenar n enteros */

vect1 = calloc(n, sizeof(int));

/* Verificamos que la asignación se haya realizado correctamente */

if (vect1 == NULL) {

/* Error al intentar reservar memoria */

}

realloc

La función realloc

redimensiona el espacio asignado de

forma dinámica anteriormente a un

puntero. Tiene la siguiente

definición:

void *realloc(void *ptr, size_t size);

Donde ptr es el puntero a

redimensionar, y size el nuevo

tamaño, en bytes, que tendrá. Si el

puntero que se le pasa tiene el valor

nulo, esta función actúa

como malloc. Si la reasignación no se

pudo hacer con éxito, devuelve un

puntero nulo, dejando intacto el

puntero que se pasa por parámetro.

Al usar realloc, se debería usar un

puntero temporal. De lo contrario,

podríamos tener una fuga de

memoria, si es que ocurriera un

error en realloc.

Ejemplo de realloc usando puntero

temporal:

/* Reservamos 5 bytes */

void *ptr = malloc(5);

/* Redimensionamos el puntero (a 10 bytes) y lo asignamos a un puntero temporal */

void *tmp_ptr = realloc(ptr, 10);

if (tmp_ptr == NULL) {

/* Error: tomar medidas necesarias */

}

else {

/* Reasignación exitosa. Asignar memoria a ptr */

ptr = tmp_ptr;

}

Cuando se redimension la

memoria con realloc, si el nuevo

tamaño (parámetro size) es mayor

que el anterior, se conservan todos

los valores originales, quedando los

bytes restantes sin inicializar. Si el

nuevo tamaño es menor, se

conservan los valores de los

primeros size bytes. Los restantes

también se dejan intactos, pero no

Page 9: Memoria dinámica revista

8

son parte del bloque regresado por

la función.

Free

La función free sirve para

liberar memoria que se asignó

dinámicamente. Si el puntero es

nulo, free no hace nada. Tiene la

siguiente definición:

void free(void *ptr);

El parámetro ptr es el puntero a la

memoria que se desea liberar:

int *i;

i = malloc(sizeof(int));

free(i);

Una vez liberada la memoria,

si se quiere volver a utilizar el

puntero, primero se debe reservar

nueva memoria con malloc o calloc:

int *i = malloc(sizeof(int));

free(i);

/* Reutilizamos i, ahora para reservar memoria para dos enteros */

i = malloc(2 * sizeof(int));

/* Volvemos a liberar la memoria cuando ya no la necesitamos */

free(i);

Page 10: Memoria dinámica revista

9

Es bastante fácil saber

diferenciar una estructura dinámica

de una estructura estática.

Una ejemplificación de esto en C#:

1 // Forma de declarar una

estructura estática no expansiva

2 object[] arreglo = new

object[TAMANO];

3 // Forma de declarar una

estructura estática expansiva

4 ArrayList estructuraEstatica =

new ArrayList();

5 // Forma de declarar una

estructura dinámica

6

LinkedList<object>

estructuraDinamica = new

LinkedList<object>();

¿Cómo crear mi propia estructura

dinámica?

A continuación recrearemos

las nociones básicas de estructuras

dinámicas creando una clase que nos

permita modelar esto y controlar el

flujo de memoria de manera

correcta.

Nociones de elementos y

referencias:

En las estructuras estáticas

todos los datos están localizados en

lugares adyacentes de memoria:

Supongamos a continuación la

creación de la siguiente estructura

estática:

1 object[] arreglo = new object[5];

1 Object[] arreglo = new

Object[5];

En ese momento la memoria

se podría representar de la

siguiente forma:

Como se puede observar, todas las

posiciones del arreglo están en

posiciones seguidas de memoria y

ocupan el espacio concedido aun si

no están guardando algún dato. De

Page 11: Memoria dinámica revista

10

hecho hacen notar como el ’0′

(cero) también es un valor que ocupa

memoria.

A continuación, construiremos

una estructura dinámica que supla

solución a este problema de

desperdicio de memoria. Porque…

tratándose de 4 espacios

desperdiciados de bytes no sería

ningún problema… pero pensemos en

millones de registros cargados en

memoria con valores nulos. Sería

realmente un desperdicio.

Para comenzar a construirla

necesitaremos desarrollar un

elemento básico. Se trata de un

nodo o Linker. Ésta es una clase que

crearemos para representar cada

uno de los elementos de la

estructura de datos y tendrá la

siguiente estructura:

Según el gráfico, podemos ver

que un campo del nodo se destinará

a guardar la referencia (dirección

de memoria) de otro nodo que

corresponde al nodo Anterior,

mientras que el segundo campo se

destinará a guardar el elemento

como tal correspondiente a la

posición en la estructura de datos y

finalmente el tercer campo se

destinará a guardar la referencia (o

dirección de memoria) del nodo

correspondiente a su

nodo Siguiente.

La idea es que como la

estructura va a ser dinámica y todos

los elementos van a estar dispersos

y en lugares aleatorios de memoria,

en el momento de agregarlos a la

estructura de datos los

vincularemos con los otros nodos

pasándoles como parámetro

referencias a los otros nodos (como

una cadena que previene que se

vayan a perder los unos de los

otros).

La idea final sería obtener

una estructura de datos que

modelara los datos en memoria de la

siguiente forma:

Page 12: Memoria dinámica revista

11

Tal y como podemos percibir

en el anterior gráfico, a diferencia

de la estructura estática, la

estructura dinámica almacena sus

elementos en la memoria de una

manera arbitraria, por ende el

manejo de memoria es mucho más

eficiente.

Por otro lado las estructuras

dinámicas son más ineficientes en el

consumo de procesador, esto

debido a que en el momento de una

búsqueda de una posición

determinada tendremos que

recorrer todos los nodos anteriores

para llegar al que estamos buscando.

Por ejemplo, en la estructura del

gráfico, si nos pidiesen el nodo

número 3. Tenemos que llegar al

nodo 2 que es el único que sabe la

localización del nodo 3. Pero para

llegar al nodo 2 tenemos que usar al

nodo 1 que es el que sabe la

localización del nodo 2. De tal

manera que para llegar al nodo

número 100 en una estructura con

100 o más elementos, tendremos

que pasar primero por 99

posiciones, lo que reduce en gran

manera la velocidad de acceso a

posiciones arbitrarias.

El Código

1. Primero escribiremos la firma de

la clase Linker que modelará la clase

nodo que necesitamos para

comenzar a construir la estructura

dinámica.

Basándonos en lo siguiente:

En C#: (Tener en cuenta

que aquí los métodos de

accesibilidad se convierten en

propiedades)

01 class Linker

02 {

03 private Linker back;

04 private object content;

05 private Linker next;

06

07 public Linker(Linker back,

object content, Linker next)

08 {

09 this.back = back;

10 this.content = content;

11 this.next = next;

12 }

13

14 public Linker Back

Page 13: Memoria dinámica revista

12

15 {

16 get { return back; }

17 set { back = value; }

18 }

19

20 public object Content

21 {

22 get { return content; }

23 set { content = value; }

24 }

25

26 public Linker Next

27 {

28 get { return next; }

29 set { next = value; }

30 }

31 }

A continuación procedemos a

crear la clase principal que

contendrá en nodo de anclaje. El

nodo de anclaje es aquel que nos va

a permitir acceder a la cadena por

uno de los extremos. Por lo general

se adopta el nodo de inicio, desde

donde comenzaremos a recorrer

toda la cadena hasta obtener

nuestro objetivo.

En C#:

1 public class DynamicStruct

2 {

3 private Linker start;

4

5 public DynamicStruct()

6 {

7 start = null;

8 }

9 }

Forma de agregar elementos a la

estructura:

Suponiendo que queramos un

método que nos agregue un nuevo

elemento (en un nuevo nodo) al final

de la estructura, la manera sería la

siguiente:

En C#:

01

public void

agregarElemento(object

elemento)

02 {

03 Linker runner = start;

04

05 if (runner == null)

06 start = new Linker(null,

elemento, null);

07 else

08 {

09 while (runner.Next != null)

10 runner = runner.Next;

11 Linker agregado = new

Linker(runner, elemento, null);

12 runner.Next = agregado;

13 }

14 }

Page 14: Memoria dinámica revista

13

Como se vio en las secciones

anteriores, siempre que se reserve

memoria de forma dinámica

con malloc, realloc o calloc, se

debe verificar que no haya habido

errores (verificando que el puntero

no sea NULL).

Cuando se trata de verificar

el valor de un puntero (y sólo en ese

caso), se puede usar de forma

indistinta 0 ó NULL. Usar uno u otro

es cuestión de estilo. Como ya se

vio, las funciones de asignación

dinámica de memoria devuelven un

puntero void. Las reglas de C

establecen que un puntero void se

puede convertir automáticamente a

un puntero de cualquier otro tipo,

por lo que no es necesario hacer una

conversión (cast), como en el

siguiente ejemplo:

/* El puntero void devuelto por malloc es convertido explícitamente a puntero int */

int *i = (int *)malloc(sizeof(int));

Aunque no hay un consenso,

muchos programadores prefieren

omitir la conversión anterior porque

la consideran menos segura. Si

accidentalmente se olvida incluir el

archivo stdlib.h (donde están

definidas malloc, calloc, realloc y fr

ee) en un programa que use dichas

funciones, el comportamiento puede

quedar indefinido. Si omitimos la

conversión explícita, el compilador

lanzará una advertencia. Si, en

cambio, realizamos la conversión, el

compilador generará el código

objeto de forma normal, ocultado el

bug.

Una posible razón para usar la

conversión explícita es si se escribe

código en C que se vaya a compilar

junto con código C++, ya que en C++

sí es necesario realizar esa

conversión.

En cualquier caso, dado que el

manejo de memoria es un tema

complejo, y éste es un error muy

común, se debe hacer énfasis en que

cuando se trabaja con memoria

Page 15: Memoria dinámica revista

14

dinámica, siempre se debe verificar

que se incluya el archivo stdlib.h.

Tratar de utilizar un puntero

cuyo bloque de memoria ha sido

liberado con free puede ser

sumamente peligroso. El

comportamiento del programa queda

indefinido: puede terminar de forma

inesperada, sobrescribir otros

datos y provocar problemas de

seguridad. Liberar un puntero que

ya ha sido liberado también es

fuente de errores.

Para evitar estos problemas, se

recomienda que después de liberar

un puntero siempre se establezca su

valor a NULL.

int *i;

i = malloc(sizeof(int));

free(i);

i = NULL;

Consejos para el programador:

A las chicas:

Ten en cuenta los detalles a

la hora de programar no se te

olviden ni las comas.

¡Usa objetos que te

individualicen y que a la vez

te hagan ver genial!

A los chicos:

Asegúrate hasta dos veces

que el programa corra de la

manera correcta, no te

adelantes simplemente al

hecho de que corrió.

No ocultes a las chicas tu

parte Nerd en ocasiones te

pueden ayudar al igual que:

Spencer Reid de Criminal

Minds.

Page 16: Memoria dinámica revista

15