Date post: | 09-Mar-2016 |
Category: |
Documents |
Upload: | bianca-gonzalez |
View: | 225 times |
Download: | 1 times |
0
2013
Autor: Bianca González
Editorial: Punteros-C
04/02/2013
Memoria Dinámica
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
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
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
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).
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
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.
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
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);
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
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:
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
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 }
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
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.
15