C
Tutorial C: Gestión de memoria dinámica
C: Gestión eficiente de memoria dinámica usando malloc, calloc, realloc y free. Evita fugas y gestiona errores con punteros nulos.
Aprende C y certifícateReservar memoria en tiempo de ejecución
Formas de reservar memoria en C:
- La función
malloc()
permite asignar un bloque continuo de memoria durante la ejecución. Toma como parámetro el tamaño requerido en bytes y devuelve un puntero de tipovoid*
. Un uso habitual consiste en asignar este puntero a una variable del tipo deseado mediante conversión explícita. Es importante verificar que la asignación no devuelvaNULL
antes de usar el bloque asignado. - La función
calloc()
se utiliza para reservar memoria para un número específico de elementos, inicializando cada byte a cero. Recibe dos argumentos: la cantidad de elementos y el tamaño de cada elemento en bytes. Al igual que conmalloc()
, conviene controlar que el resultado no seaNULL
, ya que esto implicaría que la asignación no ha sido satisfactoria. - La función
realloc()
facilita redimensionar un bloque de memoria previamente asignado. Si se solicita un tamaño mayor, esta función procurará mantener el contenido existente y reservar espacio adicional. Si se llama con un tamaño menor, ajusta el bloque conforme a la nueva longitud. En caso de que no sea posible redimensionar en la misma dirección de memoria, se copiará el contenido al nuevo bloque y el anterior se liberará de manera automática, siempre que no seaNULL
. - La función
free()
se encarga de devolver al sistema el bloque asignado conmalloc()
,calloc()
orealloc()
. Para un manejo adecuado, se recomienda guardar una referencia al área de memoria y liberar cada bloque cuando ya no sea necesario. Intentar liberar un puntero que no apunte a un bloque válido o que ya se haya liberado puede resultar en comportamientos no definidos.
Un ejemplo básico de asignación y redimensionado podría ser:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *numeros = (int *)malloc(5 * sizeof(int));
if (numeros == NULL) {
return 1;
}
// Inicialización de los valores
for (int i = 0; i < 5; i++) {
numeros[i] = i * 2;
}
// Redimensionar el bloque a 10 elementos
int *temp = (int *)realloc(numeros, 10 * sizeof(int));
if (temp == NULL) {
// Liberar la memoria anterior si la redimension falló
free(numeros);
return 1;
}
numeros = temp;
// Llenar los nuevos espacios
for (int i = 5; i < 10; i++) {
numeros[i] = i * 2;
}
// Uso de los datos
for (int i = 0; i < 10; i++) {
printf("%d ", numeros[i]);
}
free(numeros);
return 0;
}
En este ejemplo, se demuestra cómo realloc()
puede ampliar la memoria asignada anteriormente sin perder los valores ya almacenados. Es habitual verificar cuidadosamente si la asignación o la redimensión devuelven NULL
, ya que esto sirve para gestionar errores de forma segura antes de intentar escribir o leer los datos en memoria.
Cómo evitar fugas de memoria (mem leaks)
Las fugas de memoria surgen cuando se reserva espacio dinámico y no se libera antes de perder la referencia al bloque asignado. Dicho problema puede comprometer el rendimiento del sistema y, en casos extremos, empujar a la aplicación a quedarse sin recursos.
Una buena práctica consiste en mantener un registro claro de cada puntero devuelto por la asignación dinámica. Se sugiere verificar en qué parte del código se reserva memoria y, de manera explícita, planificar el momento concreto en el que se llamará a free()
correspondiente.
Es frecuente originar una pérdida de memoria al sobrescribir un puntero que apunta a un bloque gerenciado sin haber liberado primero la dirección antigua. Para minimizar riesgos, conviene usar variables temporales o comprobar cuidadosamente cada ajuste de punteros. Asimismo, establecer punteros a NULL
inmediatamente después de liberar los bloques puede evitar accesos indebidos.
A continuación se muestra un ejemplo donde ocurre una fuga de memoria por no liberar la reserva inicial:
#include <stdlib.h>
void ejemploLeaking() {
int *datos = malloc(10 * sizeof(int));
if (datos == NULL) {
return;
}
// Se pierde la referencia original sin usar free()
datos = malloc(20 * sizeof(int));
// Se vuelve a asignar memoria; la anterior no se libera
}
int main() {
ejemploLeaking();
return 0;
}
Para corregirlo, se debe liberar el bloque anterior antes de sobrescribir el puntero o, cuando sea innecesario, reutilizar la memoria ya asignada. Un enfoque adecuado sería:
#include <stdlib.h>
void ejemploCorregido() {
int *datos = malloc(10 * sizeof(int));
if (datos == NULL) {
return;
}
free(datos);
datos = malloc(20 * sizeof(int));
// Ahora se libera el bloque previo antes de reservar el nuevo
}
int main() {
ejemploCorregido();
return 0;
}
Es recomendable añadir controles exhaustivos en secciones críticas del programa, de modo que cada salida posible del flujo (incluidos retornos anticipados por error) asegure la liberación de toda la memoria en uso. De este modo, se reduce notablemente la probabilidad de incurrir en fugas.
Punteros nulos y gestión de errores en asignación dinámica
Cuando se asigna memoria en C mediante funciones como malloc()
o calloc()
, es frecuente verificar si el resultado devuelto es un puntero que apunte a una sección válida de memoria o, en caso contrario, sea NULL
. Un valor NULL
indica que la asignación ha fallado y no debe usarse dicho puntero para operar sobre la memoria, pues se incurriría en comportamientos no definidos.
Comprobar si un puntero es NULL
justo después de la asignación resulta útil para tomar medidas de gestión de errores. Por ejemplo, se puede imprimir un mensaje y retornar de la función o salir del programa en caso de que la memoria requerida no pueda reservarse. Esta estrategia previene la ejecución de bloques de código que dependan de los recursos no asignados:
#include <stdio.h>
#include <stdlib.h>
int *crearArrayEnteros(size_t n) {
int *p = malloc(n * sizeof(int));
if (p == NULL) {
fprintf(stderr, "Error: no se pudo asignar memoria\n");
return NULL;
}
return p;
}
int main(void) {
int *datos = crearArrayEnteros(10);
if (datos == NULL) {
return 1;
}
// Se procede con el uso de 'datos'
free(datos);
return 0;
}
Resulta adecuado rechazar cualquier operación posterior sobre un puntero nulo, ya que esto impide lecturas o escrituras indebidas. Asimismo, es aconsejable fijar el puntero a NULL
inmediatamente después de llamar a free()
para mantener una referencia inequívoca de que la memoria ya no está disponible y thus evitar accesos accidentales. Esto facilita, a su vez, la localización de errores lógicos ya que cualquier intento de utilizar de nuevo la dirección dará lugar a una verificación explícita de NULL
.
Todas las lecciones de C
Accede a todas las lecciones de C y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A C
Introducción Y Entorno
Primer Programa En C
Introducción Y Entorno
Estructura Básica De Un Programa En C
Sintaxis
Operadores Y Expresiones
Sintaxis
Control De Flujo
Sintaxis
Arrays Y Manejo De Cadenas
Sintaxis
Funciones
Funciones Y Punteros
Punteros
Funciones Y Punteros
Gestión De Memoria Dinámica
Funciones Y Punteros
Estructuras En C
Estructuras, Uniones Y Tipos
Uniones Y Enumeraciones
Estructuras, Uniones Y Tipos
Typedef Y Y Organización De Código
Estructuras, Uniones Y Tipos
Archivos
Io Y Archivos
E/s Binaria Y Formateo
Io Y Archivos
Manipulación Avanzada De Cadenas
Io Y Archivos
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el uso de malloc, calloc, realloc y free para la asignación dinámica de memoria.
- Aplicar técnicas para evitar fugas de memoria en proyectos.
- Reconocer y manejar punteros nulos para una gestión de errores efectiva.
- Redimensionar bloques de memoria sin pérdida de datos.