Menu

calloc y realloc en C: memoria en cero y cómo hacer crecer un array

calloc te da memoria puesta en cero y multiplica el tamaño de forma segura; realloc agranda un bloque que ya llenaste. Esta página cubre ambas, el modismo del puntero temporal que evita que un realloc fallido provoque una fuga, y cuándo calloc le gana a malloc más memset.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

malloc responde una sola pregunta: dame esta cantidad de bytes. Dos compañeras en <stdlib.h> responden las preguntas que vienen después: dame esta cantidad de bytes, limpios (calloc) y necesito que el bloque que ya tengo sea más grande (realloc).

calloc: cantidad, tamaño y ceros

void *calloc(size_t count, size_t size);

Dos argumentos en lugar de uno, y el resultado queda lleno de bytes en cero.

Ese es el caso clásico: un histograma o un array de conteos donde cada casilla debe empezar en cero. Con malloc necesitarías un bucle; calloc lo hace como parte de la reserva, muchas veces sin costo porque el sistema operativo ya entrega páginas nuevas previamente puestas en cero.

El argumento del desbordamiento

La división en dos argumentos no es cosmética. Piensa en una cantidad leída de un archivo:

size_t n = huge_value_from_input;

int *a = malloc(n * sizeof *a);     /* el producto puede dar la vuelta */
int *b = calloc(n, sizeof *b);      /* obligada a detectar el desbordamiento */

Si n * sizeof *a supera lo que size_t puede contener, da la vuelta a un número pequeño, malloc tiene éxito con un bloque diminuto y cada escritura posterior se va muy por delante del final. El estándar obliga a calloc a fallar y devolver NULL en su lugar. Cuando un tamaño viene de fuera de tu programa, esa comprobación vale la pena.

Qué significa realmente "en cero"

calloc escribe bytes en cero. Para enteros y caracteres eso es el valor cero, que es lo que quieres. Para punteros y valores de punto flotante, todos los bits en cero equivalen a un puntero nulo y a 0.0 en toda plataforma habitual, pero el estándar de C no lo promete. El código que debe ser estrictamente portable los asigna de forma explícita.

calloc frente a malloc + memset

int *a = calloc(n, sizeof *a);

int *b = malloc(n * sizeof *b);
if (b != NULL) {
    memset(b, 0, n * sizeof *b);
}

Producen el mismo resultado. Prefiere calloc: es una sola línea, hace la comprobación de desbordamiento y, para bloques grandes, puede evitar tocar la memoria por completo. Prefiere malloc a secas cuando de todas formas vas a sobrescribir cada byte: poner en cero un megabyte que vas a llenar de inmediato es puro desperdicio.

realloc: cambiar el tamaño

void *realloc(void *p, size_t newsize);

realloc devuelve un bloque de newsize bytes cuyo contenido coincide con el bloque antiguo hasta el menor de los dos tamaños. Puede extender el bloque donde está, o reservar uno nuevo, copiar y liberar el viejo. No puedes saber cuál de las dos cosas hizo, así que el único puntero que puedes usar después es el que devolvió.

El modismo del puntero tmp

Las tres líneas alrededor de tmp son toda la razón por la que esta función tiene mala fama. El atajo tentador está roto:

/* BUG: fuga el bloque original cuando realloc falla */
p = realloc(p, newsize);
if (p == NULL) {
    return;
}

Cuando realloc falla devuelve NULL y deja el bloque original reservado y sin cambios. Asignar el resultado directamente a p sobrescribe el único puntero a ese bloque, así que nunca podrá liberarse: una fuga, y una que ocurre justo cuando la memoria ya escasea. Asigna a tmp, comprueba tmp y luego confirma:

void *tmp = realloc(p, newsize);
if (tmp == NULL) {
    /* p sigue siendo válido; maneja el fallo como convenga a quien llama */
    return 0;
}
p = tmp;

Dos comportamientos más que conviene conocer: realloc(NULL, n) se comporta exactamente como malloc(n), lo que permite que una función de crecimiento maneje su primera llamada sin un caso especial. Y al agrandar, los bytes nuevos quedan sin inicializar: solo se conserva el contenido antiguo.

Hacer crecer un array: el patrón estándar

Este es el patrón que usa casi todo contenedor en C: una longitud, una capacidad y duplicar cuando se encuentran.

Dos decisiones de diseño cargan con todo el peso aquí.

Duplicar, no sumar uno. Crecer en una cantidad fija hace que cada inserción copie el array entero, así que llenar n elementos cuesta aproximadamente copias de bytes. Duplicar hace que las copias sean lo bastante raras como para que cada inserción cueste tiempo constante en promedio. El arranque cap == 0 ? 4 : cap * 2 maneja la primera inserción sin una rama aparte, porque realloc(NULL, n) es simplemente malloc.

El struct es dueño de una sola reserva. v.data se libera exactamente una vez, por quien sea dueño del struct. Dejar esa regla de propiedad por escrito (en un comentario, o emparejando cada vec_init con un vec_free) es lo que evita que un contenedor que crece tenga fugas mientras pasa entre funciones.

Encoger

realloc con un tamaño menor recorta un bloque, lo cual resulta útil después de haber leído una cantidad desconocida de datos en un búfer sobredimensionado:

Un encogimiento fallido no es un error por el que valga la pena abortar: el bloque antiguo, más grande, sigue siendo válido y sigue siendo correcto. Este es el único caso donde ignorar el fallo es la decisión correcta, pero fíjate en que igual pasa por tmp, porque la regla de no sobrescribir el puntero vivo no cambia.

Cuál elegir

Lo que quieresUsa
Bytes, sin importar el contenidomalloc(n * sizeof *p)
Una cantidad de elementos, puestos en cerocalloc(n, sizeof *p)
Una cantidad que viene de una fuente no confiablecalloc, por la comprobación de desbordamiento
El bloque que ya tienes, más grande o más chicorealloc a través de un puntero tmp
La primera reserva en un bucle de crecimientorealloc(NULL, n): sin caso especial

Las cuatro se liberan con el mismo free, exactamente una vez.

Errores comunes

  • p = realloc(p, n): fuga cuando falla. Pasa siempre por tmp.
  • Guardar un puntero antiguo al bloque. Después de realloc los datos pueden haberse movido, así que todo puntero interior guardado o dirección derivada de un índice queda obsoleto. Los índices sobreviven a una mudanza; los punteros no.
  • Suponer que los bytes nuevos están en cero. Solo calloc pone ceros. realloc deja la zona de crecimiento sin inicializar.
  • calloc(n * size, 1): eso tira a la basura la comprobación de desbordamiento para la que existe la forma de dos argumentos.
  • Liberar el puntero antiguo tras un realloc exitoso. realloc ya lo liberó; un segundo free es una doble liberación.

Preguntas frecuentes

¿Cuál es la diferencia entre calloc y malloc en C?

Dos diferencias. calloc(n, size) recibe la cantidad y el tamaño de cada elemento por separado y comprueba que multiplicarlos no desborde, mientras que malloc(n * size) puede dar la vuelta en silencio. Y calloc rellena el bloque con ceros, mientras que malloc lo deja con lo que hubiera antes.

¿Cómo funciona realloc en C?

realloc(p, newsize) devuelve un bloque del nuevo tamaño con tu contenido actual conservado hasta el menor de los dos tamaños. Puede agrandar el bloque en su sitio o moverlo, así que el puntero devuelto es el único que puedes seguir usando: el antiguo quizá ya fue liberado.

¿Por qué hay que asignar realloc a un puntero temporal?

Porque p = realloc(p, n) sobrescribe p con NULL cuando la llamada falla, y el bloque original sigue reservado sin que nada apunte a él: una fuga. Asigna primero a un tmp, comprueba si es NULL y solo entonces escribe p = tmp.

¿Cuándo debo usar calloc en lugar de malloc?

Cuando realmente necesitas la memoria en cero (un array de contadores, un struct cuyos campos deben empezar vacíos, un búfer que llenarás parcialmente) o cuando el tamaño es una cantidad por un tamaño de elemento que podría ser grande. Si de todos modos vas a sobrescribir cada byte, malloc te ahorra el trabajo de poner ceros.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR