C++
Fondo morado con hexágonos simbolizando la unión de los elementos de un vector

Introducción a la memoria dinámica: malloc y realloc

Gestionando la memoria durante la ejecución del programa se abre la posibilidad de almacenar una cantidad indefinida de elementos en una lista.


| Actualizado:
279

Introducción

La necesidad de almacenar un conjunto de elementos del mismo tipo en una lista se encuentra en casi todos los programas: desde un simple Space Invaders imprimiendo los elementos como códigos ASCII hasta cualquier videojuego reciente. Por ello, es importante tener algún método para poder almacenar de forma ilimitada tantos elementos como sean necesarios en una lista. Pero aquí surge el primer problema: ¿cuántos elementos debo y puedo almacenar?

La problemática

Si utilizamos un array podemos almacenar tantos elementos como se haya indicado en su definición, pero, ¿qué pasa si necesitamos almacenar un número indefinido de elementos? Con un array podríamos forzar el programa reservando una cantidad de elementos lo suficientemente grande para que nunca se llegue a superar. No obstante, esta no es una buena práctica por una simple razón: se está reservando espacio de memoria que nunca se va a usar.

Por eso, es importante tener algún método para poder reservar tantas posiciones dentro de una lista como se necesite, añadiendo además la posibilidad de modificar esa cantidad de posiciones (y por lo tanto la memoria reservada) durante la propia ejecución del programa. Para eso existen los vectores.

Uso de vectores

Para poder usar un vector primero será necesario declararlo. Para ello, se tendrá que hacer algo muy parecido a lo que se hace con las demás variables, ya que un vector al fin y al cabo es un tipo de variable que contiene una lista de elementos. No obstante,se debe utilizar la siguiente sintaxis que se muestra a continuación:


dataType * vectorName;

De este modo, indicamos que la variable de nombre vectorName apuntará a la primera posición de la lista que contiene el vector. En otro artículo se tratarán los punteros o apuntadores, puesto que es un aspecto de la programación que va totalmente relacionado con este. No obstante, para no desviar el tema principal del artículo, simplemente se va a dejar constancia de cómo declararlo.

Una vez se tenga el vector declarado, este no tiene asignado ningún espacio de memoria, con lo cual, será necesario indicarle el espacio de memoria que necesitará para poder satisfacer las necesidades del programa. Para ello, existen dos funciones llamadas malloc y realloc que son usadas para asignar este espacio. Existe otra función llamada calloc, pero su función es la misma que la que tiene la función malloc, con la diferencia que esta recibe un primer argumento con el número de posiciones a reservar y el segundo argumento es el tamaño de solo un elemento de este tipo. Como su finalidad es la misma que la que tiene la función malloc, en este artículo se prescindirá de ella, pero se ha considerado importante hacer una mención.

La primera de ellas asigna el espacio que recibe como parámetro sin tener en cuenta el contenido que almacenaba anteriormente. Por este motivo, esta función siempre suele usarse cuando se asigna memoria por primera vez en el vector o cuando está vacío. Si por contra, interesa conservar el contenido del vector, será necesario usar siempre la función realloc, ya que su primer parámetro es el propio vector que contiene el contenido que queremos conservar. A continuación se muestra un ejemplo práctico de ambas (se usa una estructura de datos llamada tPartner que se ha creado para un ejemplo completo que se muestra al final de este artículo).


hall->partners = (tPartner*)malloc(sizeof(tPartner));
hall->partners = (tPartner*)realloc(hall->partners, sizeof(tPartner)*(hall->numPartners + 1));

En el malloc se está multiplicando el tamaño de la estructura tPartner por 1 de forma implícita, ya que se ha supuesto que solo era necesario almacenar un socio en este ejemplo. Si fuera necesario almacenar dos socios, en la primera reserva de memoria habría que multiplicarlo por 2. De este modo, el malloc quería de la siguiente forma:


hall->partners = (tPartner*)malloc(sizeof(tPartner) * 2);

Como se puede observar en el ejemplo, el primer y único argumento de la función malloc es la cantidad de espacio de memoria que queremos almacenar. Para ello, se usa la función sizeof que devuelve el tamaño de bytes que usa el tipo de estructura tPartner en este caso. Por otro lado, la función realloc conserva el contenido del vector en el primer parámetro y multiplica el tamaño de la estructura que quiere almacenar por el número de elementos que contendrá. En el primer caso simplemente se podrá almacenar un elemento tPartner y en el segundo se podrán almacenar tantos elementos como indique el valor de hall->numPartners + 1.

Llegados a este punto, se puede observar cómo sólo se reserva la memoria necesaria que necesita el programa en ese momento en concreto. En caso que sea necesario aumentarla o disminuirla (conservando en todo momento el contenido del vector) se deberá utilizar, de nuevo, la función realloc.

Ejemplo práctico

Para poner en práctica lo explicado en este artículo, se ha propuesto un ejemplo práctico completo. En este se querrá almacenar dentro de una estructura de datos de un Hall of Fame todos los socios que se quieran añadir. Del Hall of Fame queremos saber su nombre, la lista de socios y la cantidad de socios. A su vez, de cada socio queremos saber su identificación, nombre y año de nacimiento. En el ejemplo se incluyen otros conceptos como los asserts, que serán objeto de otro artículo, por lo tanto, no se hará mención a ellos en este artículo.

Finalmente, el programa debe poder mostrar por pantalla el nombre del Hall of Fame y todos los socios junto con su información.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define MAX_CHAR	64

typedef char string[MAX_CHAR+1];

typedef struct {
	int id;
	string name;
	int year;
} tPartner;

typedef struct {
	string name;
	tPartner * partners;
	int numPartners;
} tHallOfFame;

void init_hall_of_fame(tHallOfFame * hall, string name)
{
	assert(hall != NULL);
	assert(strcmp(name, "") != 0);
	
	strcpy(hall->name, name);
	hall->numPartners = 0;
}

void add_partner(tHallOfFame * hall, int partner_id, string name, int year)
{
	assert(hall != NULL);
	assert(partner_id > 0);
	assert(strcmp(name, "") != 0);
	assert(year > 0);
	
	if (hall->numPartners == 0)
		hall->partners = (tPartner*)malloc(sizeof(tPartner));
	else
		hall->partners = (tPartner*)realloc(hall->partners, sizeof(tPartner)*(hall->numPartners + 1));
	
	if (hall->partners == NULL)
		printf("ERROR: Memory error\n");
	else
	{
		hall->partners[hall->numPartners].id = partner_id;
		strcpy(hall->partners[hall->numPartners].name, name);
		hall->partners[hall->numPartners].year = year;
		
		hall->numPartners++;
	}
}

void print_partners(tHallOfFame hall)
{
	int i;
	
	printf("===========================================\n");
	printf("Hall of fame name: %s\n", hall.name);
	printf("===========================================\n");
	
	for (i = 0;i < hall.numPartners; i++)
	{
		printf("Partner ID: %d\n", hall.partners[i].id);
		printf("\tName: %s\n", hall.partners[i].name);
		printf("\tBirth year: %d\n", hall.partners[i].year);
	}
}

int main(void)
{
	tHallOfFame hall;
	
	init_hall_of_fame(&hall, "My hall of fame");
	
	add_partner(&hall, 11002, "Ada Lovelace", 1815);
	add_partner(&hall, 11003, "Stephen Hawking", 1942);
	
	print_partners(hall);
	
	return 0;
}

Conclusión

En resumen, como se ha podido observar, las ventajas de usar vectores son múltiples, no obstante, hay que saber diferenciar los casos claros donde se deben usar y los que no. En general, cuando un programa sabe la cantidad máxima de elementos que va a necesitar almacenar en una lista necesitará un array, ya que puede indicarle el máximo de elementos dentro de los corchetes. En contraposición, cuando la situación que se plantea indica que la cantidad de elementos a almacenar es desconocida, se deberá usar un vector y modificar el espacio de memoria reservado a medida que este aumente en cantidad de elementos.

Más información

Si te ha gustado este artículo, te proponemos unos enlaces con los que profundizar sobre la idea de vectores y, en especial, sobre la gestión de memoria de estos.

Si consideras que hay algún error en este artículo háznoslo saber mediante nuestro correo [email protected].


Comparte este artículo:
Foto de perfil de Carles Gallel

Carles Gallel

TaLeR

Programador y fundador de Last2, Undefined World y Erandic. Estudiante de ingeniería informática en la UOC (Universitat Oberta de Catalunya). También tengo canal de YouTube.