D. P. I. I.

ARRAYS DINAMICOS EN C (Lenguaje C)

Fecha de publicación: 16/03/2018
Autor: Antonio Espín Herranz

697 visitas


Es este artículo técnico vamos a tratar un error que cometen algunos estudiantes al manejar la memoria dinámica en C. En este ejemplo vamos a crear un array dinámico de números enteros utilizando la función malloc de stdlib.h. En la primera versión implementaremos el código en la función main y luego partiremos ese código en funciones (aquí es donde empezarán los problemas). El programa es muy sencillo, consistirá en reservar memoria, rellenar el array con números aleatorios, imprimir y liberar memoria.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>


int main(){
	int num;
	int *p = NULL;
	int i;
	
	puts("Numero de elementos:");
	scanf("%d", &num);
	
	//p = (int *) calloc(num, sizeof(int)); Tambien es valido.
	p = (int *) malloc(sizeof(int) * num);
	if (p == NULL){
		puts("\nError de memoria");
		exit(1);
	} else
		printf("\nHe reservado en %p", p);
		
	srand(time(NULL));
	for (i = 0 ; i < num ; i++ ){
		p[i] = rand() % 100;
	}
	
	puts("\nNumeros:");
	for (i = 0 ; i < num ; i++)
		printf("%d ", p[i]);
	
	free(p);	
	p = NULL;
}

El programa funciona bien, el problema aparece cuando tenemos que partir el código en funciones. Si desde la función main simplemente quisiéramos ver llamadas a las funciones del estilo:

  • reservar(...)
  • imprimir(...)
  • ...
Una primera solución podría ser la que se indica a continuación pero no es correcta (el problema es que los alumnos piensan que a través de un puntero se puede modificar el contenido simplemente pasan el puntero como se indica en el siguiente bloque de código).

#include <stdio.h>
#include <stdlib.h>
#include <math.h>


void reservar(int *ptr, int num){
	
	ptr = (int *) malloc(sizeof(int) * num);
	if (ptr == NULL){
		puts("\nError de memoria");
		exit(1);
	} else
		printf("\nHe reservado en %p", ptr);
}

void inicializar(int *ptr, int num){
	int i;
	
	srand(time(NULL));
	for (i = 0 ; i < num ; i++ ){
		ptr[i] = rand() % 100;
	}
	
}

void imprimir(int *ptr, int num){
	int i;
	
	puts("\nNumeros:");
	for (i = 0 ; i < num ; i++)
		printf("%d ", ptr[i]);
}

void liberar(int *ptr){
	free(ptr);	
	ptr = NULL;
}

int main(){
	int num;
	int *p = NULL;
		
	puts("Numero de elementos:");
	scanf("%d", &num);
	
	reservar(p, num);
	inicializar(p, num);
	imprimir(p, num);	
	liberar(p);	
}

El código no funciona. Si lo ejecutamos en el compilador de DEV-CPP nos saldrá el error: El programa dejó de funcionar .... El problema es que estamos reservando un bloque de memoria pero NO ACTUALIZAMOS EL PUNTERO p de main. El bloque se reserva pero perdemos la dirección de ese bloque. Hay que tener en cuenta que en C la memoria dinámica se reserva en el heap y la declaración de variables, llamadas a funciones, etc. se encuentra en el stack. Para resumir el problema con un ejemplo: es como si un amigo (la función reservar) nos compra una entrada de cine y luego no nos dice donde nos tenemos que sentar (el puntero de main). En la siguiente imagen lo podemos ver.

c,arrays,punteros,memoria,dinamica,malloc,free codigo, articulos, tecnicos, programacion, informatica

A la hora de modificar el código tenemos dos posibilidades: Pasar el puntero por puntero para cambiar su contenido. Lo que implica un doble puntero o que las funciones que modifiquen el puntero que lo devuelvan a main y se lo asignamos al puntero p de main. Esto no quiere decir que tengamos que pasar el doble puntero siempre, nos vamos a complicar sin necesidad. Si la función va a imprimir como no tengo que modificar el puntero NO es necesario pasarlo por puntero. A continuación, veremos la implementación de las dos soluciones:
1. Con el doble puntero:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>


void reservar(int **ptr, int num){
	// La funcion declara un doble puntero
	
	// Acceso al contenido del puntero para reservar el bloque de memoria.
	*ptr = (int *) malloc(sizeof(int) * num);
	if (*ptr == NULL){
		puts("\nError de memoria");
		exit(1);
	} else
		printf("\nHe reservado en %p", *ptr);
}

void inicializar(int *ptr, int num){
	int i;
	
	srand(time(NULL));
	for (i = 0 ; i < num ; i++ ){
		ptr[i] = rand() % 100;
	}
	
}

void imprimir(int *ptr, int num){
	int i;
	
	puts("\nNumeros:");
	for (i = 0 ; i < num ; i++)
		printf("%d ", ptr[i]);
}

void liberar(int **ptr){	
	free(*ptr);	
	*ptr = NULL;
}

int main(){
	int num;
	int *p = NULL;
	
	puts("Numero de elementos:");
	scanf("%d", &num);
	
	// OJO, pasamos el doble puntero
	reservar(&p, num);
	inicializar(p, num);
	imprimir(p, num);	
	liberar(&p);	
}

2. La función que modifica el puntero, lo devuelve

#include <stdio.h>
#include <stdlib.h>
#include <math.h>


int *reservar(int *ptr, int num){
	// La funcion declara que devuelve un puntero.

	ptr = (int *) malloc(sizeof(int) * num);
	if (ptr == NULL){
		puts("\nError de memoria");
		exit(1);
	} else
		printf("\nHe reservado en %p", ptr);
		
	return ptr;
}

void inicializar(int *ptr, int num){
	int i;
	
	srand(time(NULL));
	for (i = 0 ; i < num ; i++ ){
		ptr[i] = rand() % 100;
	}
	
}

void imprimir(int *ptr, int num){
	int i;
	
	puts("\nNumeros:");
	for (i = 0 ; i < num ; i++)
		printf("%d ", ptr[i]);
}

int *liberar(int *ptr){
	free(ptr);	
	ptr = NULL;
	return ptr;
}

int main(){
	int num;
	int *p = NULL;
	
	puts("Numero de elementos:");
	scanf("%d", &num);
	
	// OJO, asignamos al puntero de main el valor devuelto por la funcion
	p = reservar(p, num);
	inicializar(p, num);
	imprimir(p, num);	
	p = liberar(p);	
}

A continuación, indico los cursos y un enlace para la descarga del código.


Cursos recomendados:
C01 C02

Descarga código