PUNTEROS Y ASIGNACIÓN
DINÁMICA DE MEMORIA
2011
UNAN – LEON Departamento de Computación Autor: Ing. Juan Carlos Antón Soto Asignatura: Programación Estructurada
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
1
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
Un puntero es una variable que representa la dirección de memoria de otro dato, tal
como una variable o un elemento de un array.
Los punteros están muy relacionados con los arrays y proporcionan una alternativa de
acceso a los elementos individuales del array. Proporcionan una forma conveniente para
representar arrays multidimensionales, permitiendo que un array multidimensional sea
reemplazado por un array de punteros de menor dimensión. Esta característica permite
que una colección de cadenas de caracteres sea representada con un solo array, incluso
cuando las cadenas pueden tener distintas longitudes.
Un puntero puede apuntar a un objeto de cualquier tipo, incluyendo estructuras,
funciones, etc. Los punteros se pueden utilizar para crear y manipular estructuras de
datos, para asignar memoria dinámicamente y para proveer el paso de argumentos por
referencia en las llamadas a funciones.
CREACION DE PUNTEROS
Un puntero se declara precediendo el identificador que referencia al puntero, por el
operador de indirección (*), el cual significa “puntero a”. Un puntero siempre apunta a un
tipo particular. Un puntero no inicializado tiene un valor desconocido.
tipo *identificador;
� tipo Tipo de dato del objeto referenciado por el puntero.
� identificador
Identificador de la variable de tipo puntero.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
2
Ejemplo:
int *pa; //declara una variable puntero a un tipo de dato entero
Cuando se declara un puntero se reserva memoria para albergar una dirección de memoria, pero NO PARA ALMACENAR EL DATO AL QUE APUNTA EL PUNTERO.
El espacio de memoria reservado para almacenar un puntero es el mismo independientemente del tipo de dato al que apunte.
OPERADORES
Al hablar de punteros, se distinguen dos operadores:
� Operador dirección de : &
� Operador de indirección : *
El operador &, devuelve como resultado la dirección de su operando.
El operador *, toma su operando como una dirección y nos da como resultado su
contenido.
OPERACIONES CON PUNTEROS
Operación de asignación =
A un puntero se le puede asignar una dirección de memoria concreta, la dirección de una variable o el contenido de otro puntero.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
3
Una dirección de memoria concreta: int *ptr; ... ptr = 0x1F3CE00A; ... La dirección de una variable del tipo al que apunta el puntero: char c; char *ptr; ... ptr = &c; Otro puntero del mismo tipo: char c; char *ptr1; char *ptr2; … ptr1 = &c; ptr2 = ptr1; El código siguiente explica el comportamiento de los punteros en memoria:
Suponer que la variable “y” está en la localidad de memoria 1001, “z” en 1002, “nptr”
en 1003, y “mptr” en 1007, las variables “y” y “z” son declaradas de tipo entero e inicializadas a 5 y 3 respectivamente, “nptr” y “mptr” son declarados como punteros a enteros.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
4
A “nptr” se le asigna la dirección de “y” por lo que “nptr” se carga con la dirección 1001 como se muestra en la figura.
“z” obtiene el contenido de “nptr”. En el ejemplo “nptr” apunta a la localidad de memoria 1001 (localidad de y). Por lo tanto, “z” obtiene el valor de “y”.
Se asigna el valor entero 7 al contenido del puntero “nptr”, por lo cual se hace una copia en la variable “y”, ya que éste puntero contiene la dirección de memoria de “y”.
La dirección del puntero “nptr” es asignada a “mptr”, por lo que éste puntero tendrá la misma dirección que tiene “y”, esto es igual a la siguiente instrucción “mptr” = &y.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
5
Ejemplo de uso de punteros
#include <stdio.h> void main() { int a = 1, b = 2; int *ip; /* ip es un puntero a entero */ printf("\n\n\t PRUEBA DE PUNTEROS.\n"); printf("\nValores iniciales: a = %d, b = %d \n", a, b); printf("\nDirecciones de memoria de a = %X y b = %X\n", &a, &b); printf("\ndireccion de memoria inicial en el puntero ip = %X \n", ip); printf("\nbasura en *ip = %d \n", *ip); printf("\nEl puntero ip apunta a \'a\'\n"); ip = &a; /* ip apunta a x; *ip da el valor de x */ printf("\nip = &a"); printf("\n\nip = %X, a = %d, *ip = %d\n\n", ip, a, *ip); b = *ip; /* y es ahora 1 */ *ip = 0; /* x es ahora 0 */ printf("\n\t Asignaciones \n\nb = *ip, *ip = 0\n"); printf("\nb = %d; a = %d\n\n", b, a); }
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
6
Ejemplo 2
Programa que suma un valor a una variable a través de un puntero #include <stdio.h> void main() { int a, *p; a=5; printf("\n\nEl valor inicial de a = %d y su direccion de memoria es : %X\n", a, &a); p=&a; printf("\np = &a\n\np = %X y *p = %d", p, *p); *p+=7; printf("\n\nEl contenido de *p se suma con el valor 7\n\n"); printf("\nEl valor final de la variable a = %d y el contenido de *p = %d\n\n", a,*p); }
OPERACIONES ARITMETICAS
A un puntero se le puede sumar o restar un entero.
Ejemplo: int *p; // declara p como un puntero a un entero p++; // hace que p apunte al siguiente entero p--; // hace que p apunte al entero anterior p = p + 3; // avanzar tres enteros p = p – 3; // retroceder tres enteros
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
7
COMPARACION DE PUNTEROS
Es posible comparar dos punteros en una expresión de relación. Esta operación tiene
sentido si ambos punteros apuntan a elementos del mismo array.
Ejemplo de punteros con operaciones aritméticas
#include <stdio.h> void main() { int a = 2, b = 5, c[] = {1,10,30,44}, i, *pa, *pb, *pc; printf("\nValores iniciales\n\n”); printf(“a = %d &a = %X b = %d &b = %X c = {1,10,30,44}\n", a, &a, b, &b); for (i = 0; i < 4; i++) printf("c[%d] = %d direccion de memoria : %X\n", i, c[i], &c[i]); printf("\n pa apunta a la direccion de la variable a\n\npa = &a\n"); pa = &a; // pa apunta a la direccion de a printf("pa = %X, *pa = %d\n\n",pa, *pa); printf("\nSe suma 1 al valor apuntado por pa y luego se asigna a b\n"); b = *pa + 1; // se le está sumando 1 al valor apuntado por pa y luego se asigna a b printf("b = %d\n\n",b); pc = c; //pc apunta al arreglo c printf("pc apunta al arreglo c\n pc = c\n\n pc = %X\n\n", pc); printf("el puntero pc se desplaza una posicion de memoria y asigna ese contenido a \'b\' \n "); b = *(pc + 1); // pc se desplaza una posicion de memoria y asigna ese contenido a b printf(" b = %d\n\n", b); printf("pb = &b\n"); pb = &b; printf("pb = %X , direccion de b = %X, b = %d\n\n",pb,&b,b); printf(" *pb = 0\n"); *pb = 0; // el contenido de pb es puesto a cero printf("La variable b indirectamente se incrementa en 2 unidades\n\n"); *pb += 2; // la variable b indirectamente se incrementa en dos unidades printf("*pb = %d , b = %d\n\n",*pb,b); printf("La variable b indirectamente se decrementa en 1 unidad\n\n"); (*pb) --; // La variable b indirectamente se decrementa en una unidad printf("*pb = %d , b = %d\n\n",*pb,b); }
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
8
PUNTERO NULO
Un puntero se puede inicializar como cualquier otra variable, aunque los únicos valores
significativos son NULL o la dirección de un objeto previamente definido.
NULL es una constante definida en el fichero stdio.h, cuando un puntero apunta a
NULL significa que apunta a la dirección 0 del sistema, y está a la espera de que se le
asigne una dirección válida. La siguiente sentencia no se debe realizar:
int *px = 103825;
Ya que el sistema es el encargado de asignar las direcciones y por lo tanto el usuario
no sabe acerca de la dirección 103825.
Errores comunes sobre punteros
Asignar punteros de distinto tipo int a = 10; int *ptri = NULL; double x = 5.0; double *ptrf = NULL; ptri = &a; ptrf = &x;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
9
ptrf = ptri; // ERROR
Utilizar punteros no inicializados char *ptr;
*ptr = ‘a’; // ERROR
Asignar valores a un puntero y no a la variable a la que apunta int n; int *ptr = &n; ptr = 9; // ERROR
Intentar asignarle un valor al dato apuntado por un puntero cuando éste es NULL int *ptr = NULL;
*ptr = 9; // ERROR
PUNTEROS Y ARRAYS
En C existe, entre punteros y arrays, una relación tal que, cualquier operación que se
pueda realizar mediante la indexación de un array, se puede realizar también con
punteros.
Cuando declaramos un vector
tipo identificador [dim]
En realidad
1. Reservamos memoria para almacenar dim elementos de tipo tipo.
2. Creamos un puntero identificador que apunta a la primera posición de la
memoria reservada para almacenar los componentes del array.
Por tanto, el identificador del array es un puntero.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
10
Utilizando la aritmética de punteros nos desplazamos de unas posiciones de memoria
a otras. Pero ¿cómo acceder a los contenidos de esas posiciones utilizando notación de
punteros?
Empleando el operador *, indirección que nos da el contenido de la dirección de
memoria apuntada.
Ejemplo:
Analice el siguiente programa, escrito primeramente con matrices y a continuación
con punteros.
// versión utilizando una matriz unidimensional
#include <stdio.h>
void main()
{
int lista[ ] = {24,30,15,45,34};
int indice;
for(indice = 0; indice < 5; indice ++)
printf(“\t%d”,lista[indice]);
printf(“\n\n”);
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
11
En este ejemplo se ha utilizado la indexación, expresión lista[indice], para acceder a
los elementos de la matriz lista. Cuando se interpreta ésta expresión se sabe que a partir
de la dirección de comienzo de la matriz, esto es, a partir de lista, tiene que avanzar
indice elementos para acceder al contenido del elemento especificado por ese índice.
//versión con punteros
#include <stdio.h>
void main()
{
int lista[ ] = {24,30,15,45,34};
int indice;
for(indice = 0; indice < 5; indice ++)
printf(“\t%d”,*(lista + indice));
}
Esta versión es idéntica a la anterior, excepto que la expresión para acceder a los
elementos del array es: *(lista + indice). Esto deja constancia de que lista es la dirección
de comienzo del array. Si a esta dirección le sumamos 1, dicho de otra manera si indice
vale 1, nos situamos en el siguiente entero de lista; esto es *(lista + 1) y lista[1]
representan el mismo valor.
Punteros a cadena de caracteres
Puesto que una cadena de caracteres es un array de caracteres, la forma de definir un
puntero a una cadena de caracteres es:
char *cadena;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
12
El identificador del array es la dirección de comienzo del array. Para saber dónde
termina la cadena, el compilador añade el carácter ‘\0’ (ASCII 0, NULL):
char *nombre = “PEPE PEREZ”;
Si se quiere recorrer la cadena con notación de punteros se hace lo siguiente:
#include <stdio.h> void main () { char *nombre = "PEPE PEREZ"; int i = 0; do { printf("%c", *(nombre+i)); i++; }while (*(nombre+i)); printf("\n"); }
Muchas funciones de C que trabajan con cadenas de caracteres, utilizan punteros y
devuelven como resultado un puntero. Por ejemplo, la función de C strchr() para localizar
un carácter en una cadena, devuelve un puntero al carácter buscado o un puntero nulo si el
carácter no se encuentra.
Ejemplo: #include <stdio.h> #include <string.h> int main() { const char *cadena = "FERROCARRIL"; char caracter1 = 'A'; char caracter2 = 'z';
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
13
if ( strchr( cadena, caracter1 ) != NULL ) printf( "\n\'%c\' se encuentra en %s\n\n", caracter1, cadena ); else printf( "\n\'%c\' no se encontro en %s\n\n", caracter1, cadena ); if ( strchr( cadena, caracter2 ) != NULL ) printf( "\n\'%c\' se encontro en %s\n\n", caracter2, cadena ); else printf( "\n\'%c\' no se encontro en %s\n\n", caracter2, cadena ); return 0; } El siguiente ejemplo copia una cadena de caracteres en otra, utilizando punteros.
#include <stdio.h>
void copia_cadena(char *p, char *q);
void main()
{
char cadena1[50], cadena2[50];
printf("Introducir cadena: ");
gets(cadena1);
copia_cadena(cadena2,cadena1); // copia la cadena1 en la cadena2
printf("\n\tLa cadena copiada es: %s\n\n",cadena2);
}
void copia_cadena(char *p, char *q) // copia q en p
{
while ((*p = *q) != '\0')
{
p ++;
q ++;
}
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
14
El bucle formado por la sentencia while, realiza las siguientes operaciones:
1. Toma el carácter contenido en la dirección dada por q y lo copia en la dirección dada
por p.
2. Ve si el contenido de p es el carácter nulo.
3. p pasa apuntar a la siguiente dirección, y lo mismo sucede con q. Si no se dio la
condición de terminación, el proceso se repite desde el inicio.
Matrices de punteros
Son matrices que contienen elementos de tipo puntero, es decir, cada elemento de la
matriz contiene una dirección en lugar de un dato de tipo primitivo.
Ejemplo:
#include <stdio.h>
void main()
{
int *p[5]; //matriz de 5 elementos (int *)
int b = 30; // variable de tipo int
p[0] = &b; // p[0] apunta al entero b
printf (“\n\tEl contenido de *p[0] = %d\n\n”,*p[0]);
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
15
Un array de dos dimensiones y un array de punteros se pueden usar de forma
parecida, pero no son lo mismo. Ejemplo:
int a[5][5]; //array bidimensional
int *p[5]; //array de punteros
En el ejemplo anterior, el compilador reserva memoria para un array a de 25
elementos de tipo entero y para un array p de 5 elementos declarados como punteros a
objetos de tipo entero.
Si cada uno de los objetos apuntados por los elementos del array p es a su vez un
array de 5 elementos de tipo entero, la ocupación de memoria será la de los 5 elementos
de p más la de los 25 elementos de los 5 array de enteros.
El acceso a los elementos de la matriz p puede hacerse utilizando la notación de
punteros o utilizando la indexación igual que lo haríamos con a.
Por ejemplo para asignar valores a los elementos referenciados por la matriz p y
después visualizarlos sería de la siguiente forma:
#include <stdio.h> #define F 5 #define C 5 void main() { int a[F][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}; int *p[F], i, j; //A cada posición del arreglo de punteros se le está asignando la dirección de memoria de cada posición del arreglo bidimensional. for (i = 0; i < F; i++) p[i] = a[i]; printf("\n\n");
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
16
//Impresión de los elementos almacenados en el arreglo bidimensional a través del //arreglo de punteros. for (i = 0; i < F; i++) { for (j = 0; j < C; j++) printf("%5d ", p[i][j]); printf("\n\n"); } }
¿Cómo se aplica la notación de punteros o aritmética de punteros para desplazarse en
un array bidimensional?
Existe por tanto una equivalencia:
Con subíndices Con punteros valor
p[0][0] *(*(p+0)+0) 1
p[0][1] *(*(p+0)+1) 2
p[0][2] *(*(p+0)+2) 3
p[0][3] *(*(p+0)+3) 4
.
.
.
p[4][4] *(*(p+4)+4) 25
*p representa un puntero a la primera fila, con *(p+1)+j para las direcciones y
*(*p+1)+j) para los contenidos. El segundo subíndice actúa sobre la columna.
Si en x[10][20] se quiere acceder al elemento de la fila 3 y la columna 6, se hace
escribiendo x[2][5]. Con notación de punteros, es equivalente a *(*(x+2)+5), ya que x+2
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
17
es un puntero a la fila 3. Por tanto. El contenido de dicho puntero, *(x+2), es la fila 3. Si
se desplaza 5 posiciones en esa fila se llega a la posición *(x+2)+5, cuyo contenido es
*(*(x+2)+5). Ver dibujo:
PUNTEROS A PUNTEROS
Para especificar que una variable es un puntero a un puntero, la sintaxis utilizada es la
siguiente:
Tipo **varpp;
Donde Tipo especifica el tipo del objeto apuntado después de una doble indirección
(puede ser cualquier tipo incluyendo tipos derivados) y varpp es el identificador de la
variable puntero a puntero.
Por ejemplo: int a, *p, **pp; a = 10; //dato p = &a; // puntero que apunta al dato pp = &p; // puntero que apunta al puntero que a la vez apunta al
// dato
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
18
Se dice que p es una variable con un nivel de indirección, esto es, a través de p no se
accede directamente al dato, sino a la dirección que indica dónde está el dato. Haciendo
un razonamiento similar se dice que pp es una variable con dos niveles de indirección.
Ejemplo:
#include <stdio.h> void main() { int a[3][3], *p[3], **q, i,j; for (i = 0; i < 3; i++) p[i] = a[i]; q =p; for (i = 0; i < 3; i++)
{ for (j = 0; j < 3; j++) { printf("q[%d][%d] = ",i,j); scanf("%d", &q[i][j]); } } printf("\n\n"); for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) printf("%5d ",q[i][j]); printf("\n"); } }
El nombre de un array de dos dimensiones no puede considerarse como un puntero a
puntero. La siguiente asignación daría un error.
int a[5][5];
int **p = a; //diferentes niveles de indirección
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
19
ASIGNACIÓN DINÁMICA DE MEMORIA
La asignación dinámica de memoria es una característica de C. Le permite al usuario
crear tipos de datos y estructuras de cualquier tamaño de acuerdo a las necesidades que
se tengan en el programa.
Dos de las aplicaciones más comunes son:
� Arreglos Dinámicos
� Estructuras dinámicas de datos
La asignación dinámica de memoria consiste en asignar la cantidad de memoria
necesaria para almacenar un objeto durante la ejecución del programa, en vez de hacerlo
en la fase de compilación del mismo. Cuando se asigna memoria para un objeto de tipo
cualquiera, se devuelve un puntero a la zona de memoria asignada. Según esto, lo que tiene
que hacer el compilador es asignar una cantidad fija de memoria para almacenar la
dirección del objeto asignado dinámicamente, en vez de hacer una asignación para el
objeto en sí. Esto implica declarar un puntero a un tipo de datos igual al tipo del objeto
que se quiere asignar dinámicamente. Por ejemplo, si queremos asignar memoria
dinámicamente para una matriz de enteros, implica declarar un puntero a un entero:
int *p;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
20
Funciones para asignación dinámica de memoria.
La función malloc es empleada comúnmente para intentar “tomar” una porción contigua
de memoria, es decir, asignar un bloque de memoria de size bytes consecutivos para
almacenar uno o más objetos de un tipo cualquiera.
La función malloc se encuentra en el archivo cabecera stdlib.h yEstá definida como:
void *malloc (size_t size);
Lo anterior indica que regresará un puntero del tipo void *, el cual es el inicio en
memoria de la porción reservada de tamaño size. Si no puede reservar esa cantidad de
memoria, la función regresa un puntero nulo o NULL.
Dado que void * es regresado, C asume que el puntero puede ser convertido a
cualquier tipo. El tipo de argumento size_t está definido en la cabecera stddef.h y es un
tipo entero sin signo.
Por lo tanto:
char *cp;
cp = (char *) malloc(100);
Intenta obtener 100 bytes y asignarlos a la dirección de inicio de cp.
Lo más usual es usar la función sizeof () para indicar el número de bytes y no el tipo
de argumento size_t, por ejemplo:
int *p;
p = (int *) malloc(100 * sizeof(int));
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
21
El compilador de C requiere hacer una conversión de tipo. La forma de lograr la
conversión cast es usando (char *) e (int *), que permite convertir un puntero void a un
puntero tipo char e int respectivamente.
El operador sizeof se usa para encontrar el tamaño de cualquier tipo de dato, variable
o estructura.
Ejemplo usando la función malloc ()
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *p = NULL;
int nbytes = 100;
p = (int *) malloc(nbytes);
if (p == NULL)
printf("\n\tInsuficiente espacio de memoria\n\n");
else
printf("Se han asignado %d bytes de memoria\n\n",nbytes);
}
En el siguiente ejemplo se reserva memoria para la variable ip, en donde se emplea la
relación que existe entre punteros y array (arreglos), para manejar la memoria reservada
como un array. Por ejemplo:
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
22
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *ip = NULL, i;
ip = (int *) malloc(5 * sizeof(int));
ip[0] = 1000;
for (i = 1; i < 5; i++)
scanf("%d",&ip[i]);
printf("\n\n");
for (i = 0; i < 5; i++)
printf("%5d",ip[i]);
printf("\n\n");
}
Cuando se ha terminado de usar una porción de memoria siempre se deberá liberar
usando la función free(). Esta función permite que la memoria liberada esté disponible
nuevamente quizás para otra llamada de la función malloc().
La función free() toma un puntero como un argumento y libera la memoria a la cual el
puntero hace referencia, pero no pone el puntero a NULL. Si el puntero que referencia el
bloque de memoria que deseamos liberar es nulo, la función free no hace nada. Su sintaxis
es:
void free (void *vpuntero);
Ésta función se encuentra en el archivo cabecera stdlib.h
Si la memoria liberada por free no ha sido previamente asignada por malloc, calloc, o
realloc, se pueden producir errores durante la ejecución del programa.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
23
Ejemplo:
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *p = NULL;
int nbytes = 100;
p = (int *) malloc(nbytes);
if (p != NULL)
printf("\nSe ha asignado memoria correctamente\n\n");
else
printf("\n\nNo se ha podido asignar menmoria\n");
free(p); //Libera memoria para el puntero p
}
Existen dos funciones para reservar memoria, calloc() y realloc().
Los prototipos de estas funciones son las siguientes:
void *calloc (size_t nmemb, size_t size);
void *realloc (void *ptr, size_t size);
Estas funciones se encuentran en el archivo cabecera stdlib.h
Cuando se usa la función malloc(), la memoria no es inicializada (a cero) o borrada. Si
se quiere inicializar la memoria entonces se puede utilizar la función calloc(). Se debe
observar la diferencia de sintaxis entre calloc y malloc, ya que calloc toma el número de
elementos deseados (nmemb) y el tamaño del elemento (size), como dos argumentos
individuales.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
24
Por lo tanto para asignar 100 elementos enteros que estén inicializados a cero se
puede hacer:
int *ip;
ip = (int *) calloc(100, sizeof(int));
¿Qué hace exactamente realloc? Depende de si se pide más o menos memoria de la
que ya se tiene reservada: Si se pide más memoria, intenta primero ampliar la zona de memoria asignada. Si las
posiciones de memoria que siguen a las que ocupa a en ese instante están libres, se las asigna a la variable puntero. En caso contrario, solicita una nueva zona de memoria, copia el contenido de la vieja zona de memoria en la nueva, libera la vieja zona de memoria y nos devuelve el puntero a la nueva (zona de memoria). Si no puede reasignarse una nueva memoria la función realloc devuelve NULL.
Si se pide menos memoria, libera la que sobra en el bloque reservado.
Si para el ejemplo anterior, se quiere reasignar la memoria a 50 enteros en vez de 100
apuntados por ip, se hará:
ip = (int *) realloc (ip, 50 * sizeof(int));
Uso de la función realloc
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *p = NULL, *q = NULL;
int nbytes = 100;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
25
//asigna memoria nbytes al puntero p
p = (int *) malloc(nbytes);
if (p == NULL)
printf("\n\tInsuficiente espacio de memoria\n");
printf("\n\nSe han asignado %d bytes de memoria\n",nbytes);
//reasigna el bloque para contener datos
if (nbytes == 0)
{
free (p);
printf("\nEl bloque ha sido liberado\n");
}
q = (int *) realloc (p, nbytes*2);
if (q == NULL)
{
puts("La reasignacion no ha sido posible");
puts("Se conserva el bloque original");
}
else
{
p = q;
puts("Bloque reasignado");
printf("\nNuevo tamaño %d bytes\n", nbytes*2);
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
26
free(p);
puts("Bloque de memoria liberado");
}
Matrices Dinámicas
Las matrices dinámicas son matrices que se crean durante la ejecución del programa.
Sus elementos pueden ser de cualquier tipo, al igual que las matrices estáticas.
Utilizando la técnica de asignación de memoria dinámicamente, podemos decidir
durante la ejecución cuantos elementos queremos que tenga nuestro array.
Para asignar memoria dinámicamente para una matriz se utiliza la función malloc o
calloc de la biblioteca de C.
Matrices dinámicas numéricas (Enteros o Reales)
Para crear una matriz en tiempo de ejecución, es necesario crear un puntero que
apunte a objetos del tipo de los elementos del array.
Ejemplo:
#include <stdio.h>
#include <stdlib.h>
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
27
void main()
{
int *p = NULL;
int nbytes = 0, nElementos = 0, i = 0;
do
{
printf("Numero de elementos de la matriz: ");
scanf("%d",&nElementos);
fflush(stdin);
}while (nElementos < 1);
nbytes = nElementos * sizeof(int); //Aqui se determina el tamaño del bloque de memoria
if ((p = (int *) malloc (nbytes)) == NULL)
printf("\n\nInsuficiente espacio de memoria");
printf("\n\nSe han asignado %d bytes de memoria\n\n",nbytes);
//Iniciar los elementos de la matriz a cero
for (i = 0; i < nElementos; i++)
{
p[i] = 0;
printf(“%5d”,p[i]);
}
printf("Se está liberando memoria\n\n");
//Se libera memoria para el puntero p
free(p);
system("pause");
printf("\n\n");
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
28
Para asignar memoria a una matriz de dos dimensiones, el proceso se divide en dos
partes:
� Asignar memoria para una matriz de punteros, cuyos elementos referenciarán
cada una de las filas de la matriz de dos dimensiones que desea crear.
� Asignar memoria para cada una de las filas. El número de elementos de cada fila
puede ser variable.
Para crear la matriz de punteros, primero tenemos que crear un puntero a un puntero,
puesto que sus elementos van a ser punteros a objetos de un determinado tipo.
int **m; // puntero que referencia la matriz de puntero
El paso siguiente es asignar memoria para la matriz de punteros:
m = (int **) malloc (nfilas * sizeof (int *));
Esto implica que la matriz de punteros debe tener nfilas elementos y que cada
elemento será un puntero a un entero (el primer entero de cada fila).
Para asignar memoria a cada una de las filas, suponiendo que tienen ncolumnas
elementos de tipo int:
for (f = 0; f < ncolumnas; f ++)
m[f] = (int *) malloc (ncolumnas * sizeof (int));
Ejemplo de matriz bidimensional dinámica
#include <stdio.h>
#include <stdlib.h>
void main()
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
29
{
int **ppa = NULL;
int nFilas = 0, nCols = 0,f = 0, c = 0;
do
{
printf("Numero de Filas de la matriz: ");
scanf("%d",&nFilas);
fflush(stdin);
}while (nFilas < 1);
do
{
printf("Numero de Columnas de la matriz: ");
scanf("%d",&nCols);
fflush(stdin);
}while (nCols < 1);
//Asignar memoria para el array de punteros
if ((ppa = (int **) malloc (nFilas * sizeof(int *))) == NULL)
{
printf("\n\nInsuficiente espacio de memoria");
exit(0);
}
//Asignar memoria para cada fila
for (f = 0; f < nFilas; f ++)
{
if ((ppa[f] = (int *) malloc (nCols * sizeof(int))) == NULL)
{ printf("\n\nInsuficiente espacio de memoria");
exit(0);
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
30
}
//Iniciar los elementos de la matriz a cero
for (f = 0; f < nFilas; f ++)
for (c = 0; c < nCols; c++)
*(*(ppa+f)+c) = 0;
//Impresion de datos de la matriz
for (f = 0; f < nFilas; f ++)
{
for (c = 0; c < nCols; c++)
printf("%d",*(*(ppa+f)+c));
printf("\n");
}
printf("Se está liberando memoria\n\n");
//Liberar la memoria asignada a cada fila
for (f = 0; f < nFilas; f ++)
free (ppa+f);
//Liberar la memoria asignada al array de punteros
free (ppa);
system("pause");
printf("\n\n");
}
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
31
Para liberar la memoria ocupada por el array, primero liberamos la memoria
asignada a cada una de las filas y después la asignada al array de punteros.
Matrices dinámicas de cadenas de caracteres
Una matriz dinámica de cadena de caracteres es una matriz de dos dimensiones cuyos
elementos son de tipo char. Por lo tanto su construcción es idéntica a las matrices de dos
dimensiones vistas anteriormente.
Una matriz de cadena de caracteres es un caso típico donde las filas tienen un número
de elementos variables, dependiendo esto del número de caracteres que se almacene en
cada fila.
Ejemplo # 1:
Se trata de realizar un programa que lea una frase y, conservando el orden de las
palabras que ha leído, escriba cada una de estas palabras al revés. Por ejemplo:
“Hace mucho calor en el verano”
Se transformaría en:
“ecaH ohcum rolac ne le onarev”
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXFRA 10 void main(void) { char c, temp[20]; char **frase; int j=0, i, npal=0, n; /* reservar espacio para el vector de punteros a las palabras */ frase = malloc(MAXFRA*sizeof(char*));
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
32
printf("Introduzca una frase, y pulse \"Intro\" para finalizar.\n"); /* Lectura de las palabras */ do { c = getchar(); if (c != ' ' && c != '\n') temp[j++]=c; else { temp[j++] = '\0'; frase[npal] = malloc(j*sizeof(char)); strcpy(frase[npal++], temp); j=0; } } while (c != '\n'); printf("\n\nLas palabras de la frase son:\n"); for (j=0; j<npal; j++) printf("\t%s ", frase[j]); printf("\n\n"); printf("\n\nLas palabras al revés son:\n"); for (i=0; i<npal; i++) { printf("\t"); for (j=strlen(frase[i])-1; j>=0; j--) printf("%c", frase[i][j]); printf(" "); } printf("\n\n"); }
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
33
Ejemplo #2:
/*Matriz dinámica de cadena de caracteres*/ #include <stdio.h> #include <stdlib.h> int LeerCadenas(char **nombre, int nFilas); void VisualizarCadenas(char **nombre, int nFilas); void main() { char **nombre = NULL; int nFilas = 0; int correcto = 0, filas = 0, f = 0; do { printf("\n\tNumero de filas de la matriz: "); correcto = scanf("%d",&nFilas); fflush(stdin); }while (!correcto || nFilas < 1); //Asignar memoria para la matriz de punteros if ((nombre = (char **)malloc(nFilas * sizeof(char *))) == NULL) { printf("\n\nInsuficiente espacio de memoria\n"); exit(0); } filas = LeerCadenas(nombre, nFilas); if (filas == -1) exit(0); VisualizarCadenas(nombre, filas); //Liberar la memoria asignada a cada una de las filas for (f = 0; f < filas; f++) free (nombre[f]); //Liberar la memoria asignada a la matriz de punteros free(nombre); } int LeerCadenas(char **nombre, int nFilas) { int f = 0, longitud = 0;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
34
char cadena[81]; printf("\n\tIntroducir cadenas de caracteres\n\n"); printf("para finalizar introduzca una cadena nula.\n"); printf("Esto es, pulse solo Enter.\n\n"); while(f < nFilas && (longitud = strlen(gets(cadena))) > 0) { //Asignar espacio para una cadena de caracteres if((nombre[f] = (char *)malloc(longitud + 1)) == NULL) { printf("Insuficiente espacio de memoria disponible\n"); return -1; } //copiar la cadena en el espacio de memoria asignada strcpy(nombre[f], cadena); f++; } return (f); } void VisualizarCadenas(char **nombre, int filas) { int f = 0; printf("\n\tVisualizacion de datos\n\n"); for(f = 0; f < filas; f++) printf("%s\n", nombre[f]); printf("\n\n"); }
Punteros a estructuras
Los punteros a estructuras se declaran igual que los punteros a otros tipos de datos.
Para referirse a un miembro de una estructura apuntada por un puntero hay que utilizar el
operador ‘->’.
El hecho de declarar un puntero a una estructura no significa que dispongamos de la
estructura; es necesario asignar al puntero un bloque de memoria del tamaño de la
estructura donde se almacenaran los datos de la misma.
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
35
Ejemplo:
#include <stdio.h>
#include <string.h>
struct estudiante
{
char nombre[30];
char carnet[11];
int nota[5];
};
void main()
{
struct estudiante est, *pest;
int n, sum = 0;
float prom;
pest = &est;
printf("Nombre Estudiante: ");
gets(pest->nombre);
printf("No. Carnet: ");
fflush(stdin);
gets(pest->carnet);
printf("***Calificaciones del estudiante***\n");
for(n = 0; n < 5; n++)
{
printf("Nota[%d]: ", n+1);
scanf("%d",&pest->nota[n]);
sum += pest->nota[n];
}
prom = sum / 5;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
36
printf("\n****DATOS DEL ESTUDIANTE****\n");
printf("Nombre: %s\n",pest->nombre);
printf("No. Carnet: %s\n", pest->carnet);
printf("Promedio: %.f\n", prom);
}
Punteros a arrays de estructura
También se pueden usar punteros con arrays de estructuras. La forma de trabajar es
la misma, lo único que se tiene que hacer es asegurarse que el puntero inicialmente apunte
al primer elemento, luego saltar al siguiente hasta llegar al último.
Ejemplo:
#include <stdio.h> #define ELEMENTOS 3 struct estructura_amigo { char nombre[30]; char apellido[40]; char telefono[10]; int edad; }; struct estructura_amigo amigo[] = { "Juanjo", "Lopez", "504-4342", 30, "Marcos", "Gamindez", "405-4823", 42, "Ana", "Martinez", "533-5694", 20 };
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
37
struct estructura_amigo *p_amigo; int main() { int num_amigo; p_amigo = amigo; /* apuntamos al primer elemento del array */ /* Ahora imprimimos sus datos */ for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) { printf( "El amigo %s ", p_amigo->nombre ); printf( "%s tiene ", p_amigo->apellido ); printf( "%d años ", p_amigo->edad ); printf( "y su teléfono es el %s.\n" , p_amigo->telefono ); /* y ahora saltamos al siguiente elemento */ p_amigo++; } } En vez de p_amigo = amigo; se podía usar la forma p_amigo = &amigo[0]; es decir que
apunte al primer elemento (elemento 0) del array. La primera forma es la más usada pero la segunda quizás indica más claramente lo que se pretende.
Ahora se verá como introducir datos en un array de estructuras mediante punteros: #include <stdio.h> #define ELEMENTOS 3 struct estructura_amigo { char nombre[30]; char apellido[40]; int edad; }; struct estructura_amigo amigo[ELEMENTOS], *p_amigo;
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
38
int main() { int num_amigo; /* apuntamos al primer elemento */ p_amigo = amigo; /* Introducimos los datos mediante punteros */ for ( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) { printf("Datos amigo %i\n",num_amigo); printf("Nombre: ");fflush(stdin); gets(p_amigo->nombre); printf("Apellido: ");fflush(stdin); gets(p_amigo->apellido); printf("Edad: ");fflush(stdin); scanf( "%d", &p_amigo->edad ); /* saltamos al siguiente elemento */ p_amigo++; } /* Ahora imprimimos sus datos */ p_amigo = amigo; for( num_amigo=0; num_amigo<ELEMENTOS; num_amigo++ ) { printf( "El amigo %s ", p_amigo->nombre ); printf( "%s tiene ", p_amigo->apellido ); printf( "%d años.\n", p_amigo->edad ); p_amigo++; } }
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
39
EJERCICIOS PROPUESTOS
1. Calcule la longitud de una cantidad de caracteres ingresada por el teclado y visualice las
letras que contiene.
2. Escriba un programa en C que cuente el número de veces que aparece un carácter en
una cadena de N caracteres. Utilice asignación dinámica de memoria y recuerde liberar
dicha memoria.
3. Realice un programa que pida por teclado el tamaño de un vector y que almacene en él
números enteros aleatorios. Para recorrer el vector se pide utilizar punteros en lugar
de índices.
• El vector se crea en tiempo de ejecución, una vez que el usuario ha elegido el
tamaño.
• La función int rand(void) devuelve un número entero seudoaleatorio. Su prototipo
se encuentra en el fichero de cabecera stdlib.h
La función rand() genera una secuencia de números seudoaleatorios. Cada vez que se
invoca a esta función, devuelve un entero comprendido entre 0 y la macro
RAND_MAX definida en stdlib.h.
Se dice que rand() devuelve números seudoaleatorios porque siempre da los mismos
valores en el mismo orden. Para evitar que la secuencia aleatoria comience siempre
por la misma posición se puede utilizar la función srand() cuyo prototipo se
encuentra en stdlib.h y tiene la forma:
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
40
void srand(unsigned semilla), esta función define un punto de inicio para la
secuencia generada por rand(); este punto de inicio viene dado por el parámetro
semilla.
Puede consultar la bibliografía de C para ver ejemplos sobre su funcionamiento.
4. Se pide crear un programa que pida una serie de números al usuario y halle el máximo, el
mínimo y el promedio de todos los datos. Para ello se debe crear una variable puntero
tipo float, pedir al usuario que introduzca el número de datos, y después los datos a
calcular. Se debe reservar memoria de forma dinámica para almacenar el vector de
dato.
5. Sea notas una matriz de 3 x 10 que almacene las calificaciones de los 10 alumnos de una
clase en cada una de las 3 asignaturas que cursan. Se pide diseñar una función que
reciba por parámetro las calificaciones obtenidas por los alumnos en una determinada
asignatura y que devuelva la calificación promedio. Hacer un programa que inicialice la
matriz con valores dados y que invoque a la función para calcular la nota promedio de
cada una de las asignaturas, imprimiendo el resultado por pantalla.
���� Utilice el siguiente prototipo de la función que calcula la nota promedio; recibe por parámetro la fila de la matriz que corresponde a la asignatura cuya nota promedio se va a calcular, double calculoPromedio (double * );
6. Cree una matriz bidimensional dinámicamente con N filas y M columnas e ingrese tanto
en la diagonal principal como en la secundaria unos y en las demás posiciones ceros.
Recuerde que las filas y columnas deben ser iguales, luego, imprima la matriz
resultante.
La impresión debe ser algo así:
PUNTEROS Y ASIGNACION DINAMICA DE MEMORIA
41
Matriz de 4 x 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
7. Hacer un programa que llene una matriz de nxm dinámicamente y almacene en su
diagonal principal el número correspondiente a la fila y además pida un carácter que
rellenará el resto de la matriz, ejemplo:
Filas: 3
Columnas: 3
Carácter de relleno: *
Matriz 3x3
0 * *
* 1 *
* * 2
8. Crear un programa en C que contenga una estructura denominada “Alumno”, en la que
deben considerarse como sus campos el nombre del alumno, el sexo (f o m) y la edad. El
programa debe permitir el ingreso de una cantidad finita de alumnos, calcular la
cantidad de varones y mujeres que hay en el grupo, y la edad mayor del alumno, luego
deberá visualizar la cantidad de mujeres y varones así como la información del alumno
cuya edad es mayor.