6605 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:
#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.
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.