Memoria dinámica - Heap

¿Qué es?

Es memoria que:

En C, el programador es responsable de:

¿Para qué se usa?

Se usa cuando:

Trabajamos con:

Ejemplo:


int n;
scanf("%d",&n);
int v[n]; // NO estandar solo en algunos compiladores

La forma correcta y portable:

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

Asignación diámica de memoria:

Se hace principalmente con funciones de <stdlib.h>, por lo cual deberemos incluirla cuando deseamos manejar la memoria dinámicamente.

Funciones:

Función malloc:

¿Qué es?:

malloc significa memory allocation.

Sintaxis:

void *malloc(size_t size);

¿Por qué devuelve void *?

En C no es obligatorio castear, pero suele pedirse:

int *p = (int *) malloc(sizeof(int));
int *v = malloc(n * sizeof(int)); // Para arrays
vector = (int *) malloc((size_t)n * sizeof(int)); // Es una forma mas robusta, el compilador convierte n implícitamente a size_t lo que puede emitir warnings si n es negativo, de esta forma ya casteamos correctamente nosotros.

Verificación de error (OBLIGATORIO)

Siempre hay que comprobar si malloc devolvió NULL:


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

if (p == NULL)
{
    printf("Error: no se pudo asignar memoria\n");
    return 1;
}

No hacerlo es un error grave.

Acceso a la memoria asignada

Una vez asignada, se usa como cualquier puntero:


*p = 10;
printf("%d\n", *p);

Para arrays dinámicos:

v[i] = i * 2;

Liberación de memoria free

Toda memoria solicitada con malloc DEBE liberarse:


free(p);
p = NULL;

Evita:

Errores comunes con malloc:

Ejemplo:


int *p;

p = (int *) malloc(sizeof(int)); // Pedimos memoria para el puntero p

if (p == NULL) // Verificamos que malloc no devolvio NULL
{
    printf("No se pudo asignar memoria\n");
    return 1;
}

*p = 25; // Pasamos por valor un dato a la direccion del puntero
printf("Valor: %d\n", *p);

free(p);
p = NULL; // Liberamos la memoria solicitada

Dangling pointer - puntero colgado o suelto:

Un puntero que sigue apuntando a una zona de memoria que ya fue liberada o que dejó de ser válida.

En otras palabras:

Ejemplo:


int *p = malloc(sizeof(int)); // Reservamos memoria
*p = 10;
free(p); // Liberamos del heap la memoria solicitada, dejando de ser valida
printf("%d\n", *p); // Error: p es un dangling pointer

¿Cómo se evita?


int *p = malloc(sizeof(int)); // Reservamos memoria
*p = 10;
free(p); // Liberamos del heap la memoria solicitada, el puntero queda colgado (dangling)
p = NULL; // NULL no apunta a memoria valida
printf("%d\n", *p); // Acceder a *p cuando p == NULL es facil de detectar.

Asignar NULL invalida el puntero y evita su uso accidental.

Teoría aplicada

Makefile:


CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -pedantic

PROG = dinamica
OBJ = main.o

$(PROG): $(OBJ)
	$(CC) $(OBJ) -o $(PROG)

$(OBJ): main.c
	$(CC) $(CFLAGS) -c main.c

.PHONY: clean
clean:
	del $(OBJ) $(PROG).exe

Código:


// gcc -std=c11 -Wall -Wextra -pedantic main.c -o dinamica

#include <stdio.h>
#include <stdlib.h> // Se agrega la libreria estandar para trabajar con memoria dinamica

/*
     Las llaves auxiliares se utilizan para aislar los ejemplos,
     limitando el alcance de las variables
     y mostrar usos independientes de la memoria dinamica.
     Se pueden omitir.
*/
int main(void)
{
    /* ---- ASIGNACION DINAMICA DE UNA VARIABLE SIMPLE (int) ---- */
    {
        int *p;

        /* El casteo no es obligatorio en C, pero se usa con fines didacticos */
        p = (int *) malloc(sizeof(int)); // Pedimos memoria para el puntero p

        if (p == NULL) // Verificamos que malloc no devolvio NULL
        {
            printf("No se pudo asignar memoria\n");
            return 1;
        }

        *p = 25; // Pasamos por valor un dato a la direccion del puntero
        printf("Valor almacenado en *p = %d\n\n", *p);

        free(p); 
        p = NULL; // Liberamos la memoria solicitada
    }

    /* ---- ASIGNACION DINAMICA DE UN VECTOR DE ENTEROS ---- */
    {
        int *vector;
        int n, i;

        printf("Ingrese la cantidad de elementos: "); // Solicitamos n al usuario, para asi luego solicitar la memoria correspondiente
        // Validar scanf
        if (scanf("%d", &n) != 1 || n <= 0)
        {
            printf("Entrada invalida\n");
            return 1;
        }

        vector = (int *) malloc(n * sizeof(int)); // Pedimos memoria para el puntero vector
        
        if (vector == NULL) // Verificamos que malloc no devolvio NULL
        {
            printf("Error: no se pudo asignar memoria para el vector\n");
            return 1;
        }

        // Carga del vector
        for (i = 0; i < n; i++)
        {
            vector[i] = i + 1;
        }

        // Mostrar contenido
        printf("Contenido del vector:\n");
        for (i = 0; i < n; i++)
        {
            printf("vector[%d] = %d\n", i, vector[i]);
        }

        free(vector); // Liberamos la memoria solicitada del heap
        vector = NULL; // Evitamos dangling pointer
    }

    return 0;
}
Ejercicios de Memoria dinámica