Buffer dinámico en C: ejercicio resuelto

Buffer dinámico en C: ejercicio resuelto

Si buscas buffer dinámico en C ejercicio resuelto, aquí tienes la implementación del patrón clásico de array dinámico: malloc inicial, realloc cuando se agota la capacidad y free al terminar. La estrategia de duplicar la capacidad garantiza que el coste amortizado de inserción es O(1).

Este patrón es la base de estructuras como std::vector en C++ o ArrayList en Java, implementadas manualmente en C.

Enunciado

Implementa un array dinámico de enteros con:

  1. buf_crear(cap_inicial): crea el buffer.
  2. buf_agregar(b, val): añade un elemento, duplicando la capacidad si es necesario.
  3. buf_imprimir(b): muestra todos los elementos.
  4. buf_liberar(b): libera la memoria.

Añade los enteros del 1 al 10 partiendo de una capacidad inicial de 2 para forzar varios realloc.

Solución en C

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int    *datos;
    int     tamaño;
    int     capacidad;
} Buffer;

Buffer *buf_crear(int cap_inicial) {
    Buffer *b = malloc(sizeof(Buffer));
    if (!b) return NULL;
    b->datos     = malloc(cap_inicial * sizeof(int));
    if (!b->datos) { free(b); return NULL; }
    b->tamaño    = 0;
    b->capacidad = cap_inicial;
    return b;
}

int buf_agregar(Buffer *b, int val) {
    if (b->tamaño == b->capacidad) {
        int nueva_cap = b->capacidad * 2;
        int *tmp = realloc(b->datos, nueva_cap * sizeof(int));
        if (!tmp) return -1;          /* no cambia b->datos si falla */
        b->datos     = tmp;
        b->capacidad = nueva_cap;
        printf("  [realloc] capacidad -> %d\n", nueva_cap);
    }
    b->datos[b->tamaño++] = val;
    return 0;
}

void buf_imprimir(const Buffer *b) {
    printf("Buffer [%d/%d]: ", b->tamaño, b->capacidad);
    for (int i = 0; i < b->tamaño; i++) printf("%d ", b->datos[i]);
    printf("\n");
}

void buf_liberar(Buffer *b) {
    if (!b) return;
    free(b->datos);
    free(b);
}

int main(void) {
    Buffer *b = buf_crear(2);
    if (!b) { perror("buf_crear"); return 1; }

    for (int i = 1; i <= 10; i++) {
        if (buf_agregar(b, i) != 0) {
            fprintf(stderr, "Error al agregar %d\n", i);
            buf_liberar(b);
            return 1;
        }
    }

    buf_imprimir(b);
    buf_liberar(b);
    return 0;
}

Resultado esperado

1
2
3
4
  [realloc] capacidad -> 4
  [realloc] capacidad -> 8
  [realloc] capacidad -> 16
Buffer [10/16]: 1 2 3 4 5 6 7 8 9 10 

Errores frecuentes

  • Asignar el resultado de realloc directamente a b->datos: si realloc devuelve NULL, se pierde el puntero original y no es posible liberarlo, causando una fuga de memoria. Siempre usar una variable temporal tmp.
  • No liberar b->datos antes de liberar b: free(b) solo libera la estructura contenedora, no el array interno.
  • No verificar el resultado de malloc ni de realloc: acceder a un puntero NULL es comportamiento indefinido.
  • Usar un factor de crecimiento de 1 (añadir solo 1 elemento de capacidad): provoca O(n²) en tiempo total de inserción porque cada inserción necesita un realloc.

Aplicación práctica

El array dinámico es la estructura de datos más usada en la práctica: leer líneas de longitud variable, almacenar resultados de consultas cuyo tamaño no se conoce de antemano, implementar pilas y colas de tamaño variable, y como bloque básico de construcción de parsers y compiladores.

Siguiente ejercicio recomendado

Práctica guiada y libro completo

Si quieres una ruta completa con progresión real de dificultad:

FAQ

¿Por qué se duplica la capacidad en lugar de aumentarla en un número fijo?

Con un incremento fijo de k, para insertar n elementos se necesitan n/k llamadas a realloc, cada una copiando todos los elementos anteriores. El coste total es O(n²/k) = O(n²). Al duplicar, se necesitan log₂(n) llamadas y el coste amortizado por inserción es O(1), con coste total O(n).

¿Qué ocurre si realloc no puede obtener memoria contigua?

realloc puede mover el bloque a otra zona de memoria: copia los datos, libera el bloque original y devuelve el nuevo puntero. Si no hay memoria disponible, devuelve NULL y el bloque original permanece intacto. Por eso hay que usar una variable temporal antes de asignar.

¿Cuándo usar calloc en lugar de malloc para el buffer?

Usa calloc si necesitas garantizar que los elementos están inicializados a cero (por ejemplo, para detectar valores no escritos o para compatibilidad con código que asume ceros). Para un array donde se va a escribir en todos los elementos antes de leerlos, malloc es suficiente y ligeramente más rápido.