Tema 2 - Gestión dinámica de memoria
Punteros en profundidad, las variables dinámicas, direcciones de memoria y la gestión de la memoria (pila (stack) y montículo (heap)).
Organización de la memoria de un programa
Sección titulada «Organización de la memoria de un programa»Existen dos lugares en memoria para almacenar elementos: la pila (stack) y el montículo (heap).
| Nombre | Función |
|---|---|
| Código | El código compilado. |
| Datos estáticos | Datos estáticos compilados. |
| Pila (stack) | Se utiliza para la llamada de funciones y punteros, ahí se almacenan sus direcciones, parámetros, constantes locales, etc… |
| … | Espacio para crecer. |
| Montículo (heap) | Parte de la memoria que no está ligada a lo guardado en la pila, se utiliza para las variables dinámicas, la memoria se reserva cuando se solicita (malloc). |
Definición de variables de tipo puntero
Sección titulada «Definición de variables de tipo puntero»- Un puntero es un tipo básico en C (como
int,bool,float,etc.) y como tal ocupa en memoria una cantidad determinada (generalmente 4 bytes). - Una variable de tipo puntero contiene una dirección de memoria en la cual se almacena **una variable de otro tipo **.
- Las variables de tipo puntero en C se declara para que apunte a un tipo particular de
datos (
int,float,etc.) y no puede apuntar a ningún otro.
// Estructuratypedef tipoApuntado* tipoPuntero// Otro ejemplotypedef int* tPEntero// tPEntero es un puntero a inttPEntero P// Has declarado un puntero P// que no apunta a nada todavíaReserva y destrucción dinámica de memoria
Sección titulada «Reserva y destrucción dinámica de memoria»Inicialización de punteros
Sección titulada «Inicialización de punteros»typedef int* tPEntero;tPEntero P1;int* P2;-
tPEnterolas variables declaradas con este nuevo tipo contendrán direcciones de memoria de **variables enteras **. -
P1se reserva memoria en la pila para guardar una dirección de memoria (4 bytes) que apuntara a un entero del montículo. -
No es necesario definir un tipo para declarar un entero (como se ve en
int* P2), pero se recomienda hacerlo de esta manera para aumentar la abstracción. -
La memoria es reservada, pero no se elimina los contenidos que ya hubiera en esa zona. Inicialmente,
P1oP2contendrá un valor basura.Utilizar punteros con contenido basura puede tener efectos fatales. Por lo tanto, es mejor inicializarlos a nulo
P = NULL;
La variable dinámica
Sección titulada «La variable dinámica»Creación de una variable dinámica
Sección titulada «Creación de una variable dinámica»Para crear la variable dinámica apuntada por un puntero se utiliza el operador malloc definido en el archivo de
cabecera stdlib.
#include <stdlib>P = malloc(sizeof(int));// Memoria suficiente para un enteroflowchart TB
subgraph Memoria
subgraph Stack
subgraph P
Dirección[3F5000AC o NULL]
end
end
subgraph Heap
subgraph Variable[int = *P]
Contenido[ ]
end
end
end
Dirección --> Variable
Destrucción de una variable dinámica
Sección titulada «Destrucción de una variable dinámica»Para liberar la memoria de la variable dinámica se utiliza free definido en el archivo de cabecera stdlib.
Se marcará la variable a la que apunta el puntero como liberada, aunque la información no es destruida, ya no es accesible.
#include <stdlib>free(P);flowchart TB
subgraph Memoria
subgraph Stack
subgraph P
Dirección[3F5000AC o NULL]
end
end
subgraph Heap
subgraph Variable[Memoria liberada]
Contenido[ ]
end
end
end
Dirección -- X --> Variable
Acceso a una variable dinámica
Sección titulada «Acceso a una variable dinámica»// Declaraciónint i;int* p;
// Creación/* - - - */p = malloc(sizeof(int))
// Escriturai = 5;*p = 5;
// Lecturaj = i;j = *p;Asignación y comparación de punteros
Sección titulada «Asignación y comparación de punteros»Asignación de valores a punteros
Sección titulada «Asignación de valores a punteros»// Tenemos dos variables dinámicas P y QP = NULL; // Puntero a valor nuloP = malloc(sizeof(int)); // Se reserva la memoria (se le asigna dirección de memoria)*P = 3; // Se le asigna un entero// Q tenía un valor previo *Q = 4;P = Q; // Asignamos al puntero el valor de otro puntero*P = 11;printf("%d",*Q); // Resultado: 11Es decir ahora P no tiene la dirección de memoria que se le asignó al liberar la memoria, tiene la
de Q. Visualmente:
---
title: P = NULL;
---
flowchart LR
subgraph Stack
P
Q
end
subgraph Heap
11
end
Q --> 11
---
title: "P = malloc(...);"
---
flowchart LR
subgraph Stack
P
Q
end
subgraph Heap
__[ ]
11
end
P --> __[ ]
Q --> 11
---
title: "*P = 3;"
---
flowchart LR
subgraph Stack
P
Q
end
subgraph Heap
3
4
end
P --> 3
Q --> 4
---
title: P = Q;
---
flowchart LR
subgraph Stack
P
Q
end
subgraph Heap
3
4
end
P --> 4
Q --> 4
---
title: "*P = 11;"
---
flowchart LR
subgraph Stack
P
Q
end
subgraph Heap
3
11
end
P --> 11
Q --> 11
Comparación de punteros
Sección titulada «Comparación de punteros»// Identidadprintf(P == Q); // Solo si tienen la misma dirección de memoria// Igualdadprintf(*P == *Q); // Solo si su contenido es el mismo---
title: Identidad
---
flowchart LR
subgraph P
dirP[3F5000AC]
end
subgraph Q
dirQ[3F5000AC]
end
subgraph valor[*P *Q]
11
end
dirP & dirQ --> valor
---
title: Igualdad
---
flowchart LR
subgraph P
dirP[3F5000AC]
end
subgraph Q
dirQ[600A232B]
end
subgraph valorP[*P]
vP[11]
end
subgraph valorQ[*Q]
vQ[11]
end
dirP --> valorP
dirQ --> valorQ
Resumen sobre el uso de punteros y variables dinámicas
Sección titulada «Resumen sobre el uso de punteros y variables dinámicas»Definición y declaración
typedef int* pEntero;pEntero p;Inicialización / Creación
p = NULL;// O para las Variables dinámicap = malloc(sizeof(int));Se recomienda inicializar siempre los punteros, para no dejar valores basura.
Destrucción de una variable
free(p); // Liberar memoria (borrar valor)p = NULL;Siempre se debe hacer para crear un programa más eficiente en memoria.
Contenido de la variable dinámica
*p = valor; // Cambiar el valorvariable = *p; // Copiar el valorAsignación de valores a punteros
p = NULL; // Dirección nulap = malloc(sizeof()); // Dirección de memoria recién liberadap = q; // Dirección de otro punteroComparación de punteros
p == q; p != q; // Comparación entre punterosp == NULL; p != NULL; // Comparación con valor nuloPaso de punteros como parámetros
Sección titulada «Paso de punteros como parámetros»En C por defecto las variables se pasan por valor, no existe el paso por referencia, para emularlo se emplean direcciones de memoria (punteros). He aquí algunos ejemplos de los diferentes pasos de parámetros.
Paso de punteros por valor
Sección titulada «Paso de punteros por valor»// Esta función devuelve 0 o 1 dependiendo de si el puntero contiene NULLint IsNull (tPInteger p) { if (p == NULL) return 1; else return 0;}// Otra opción más correcta seríaint IsNull (tPInteger p) { return (p == NULL);}Paso de punteros por referencia
Sección titulada «Paso de punteros por referencia»// Función Swap que intercambia las direcciones de memoria de dos punterosvoid Swap (tPInteger* p, tPInteger* q) { tPInteger t;
if (!IsNull(*p) && !IsNull(*q)) { t = *p; *p = *q; *q = t; }}Paso de variables dinámicas como parámetros
Sección titulada «Paso de variables dinámicas como parámetros»Paso de variables por valor
Sección titulada «Paso de variables por valor»// Función printscr que imprime el contenido de una variable dinámica enteraint *p;
int printscr1 (int m) { printf("%d \n",m);}
int main () { ... printsrc1(*p); ...}
// Otra opción sería
int *p;
int printscr2 (int* m) { printf(" %d \n",*m);}void main () { ... printscr2(p); ...}Paso de variables por referencia
Sección titulada «Paso de variables por referencia»// Función readInteger para rellenar una variable dinámicaint *p;int a;
void main () { ... CreateVariable(&p); readInteger(p); readInteger(&a); ...}
void readInteger (int* m) { printf(”Give me an integer value: \n”); scanf(” %d”, m);}Errores más comunes en el manejo de punteros
Sección titulada «Errores más comunes en el manejo de punteros»Avisos de compilación más frecuentes (Warnings)
Sección titulada «Avisos de compilación más frecuentes (Warnings)»-
Las variables puntero solo pueden apuntar a datos de un tipo particular. Por lo tanto, para que los punteros puedan compararse o asignarse entre sí tienen que ser del mismo tipo.
-
Confundir el puntero (p) con la variable a la que apunta (*p).
Errores de ejecución más frecuentes (Errors)
Sección titulada «Errores de ejecución más frecuentes (Errors)»-
Las variables referenciadas por puntero (variables dinámicas) solo existen cuado se inicia el apuntador mediante la asignación a una variable que ya existe o mediante
malloc(). Un error muy frecuente es intentar acceder a la variable cuando no existe.// Incorrectotypedef int* tPos;tPos p;...*p = ...// Correctotypedef int* tPos;tPos p, q;...p = malloc(sizeof(int));*p = ...// O bienq = malloc(sizeof(int));p = q;*p = ... -
Cuando tenemos varios punteros que apuntan a la misma variable
*q = 3; p = q; j = q; *j = 5;modificar cualquiera de ellosq, p o jsobrescribirá el contenido de todas.Si cualquiera de ellos libera su memoria
free(p), todos quedarán des referenciados. -
La memoria de un ordenador es grande pero no ilimitada. Un programa eficiente es aquel que usa el mínimo de memoria necesaria. Por eso debemos emplear
free()en aquellos punteros que vayamos a modificar. Consejos:- Dejar variables a las que no apunta ningún puntero, sin haber hecho un
free(). - Hacer
free()y no preocuparnos de que apunte aNULL. - Hacer un
free()y tratar de acceder posteriormente al puntero.
- Dejar variables a las que no apunta ningún puntero, sin haber hecho un