PUNTEROS Y GESTIÓN DINÁMICA DE
MEMORIA
CONCEPTOS DE PUNTEROS EN C
Un puntero es una variable que
contiene como valor una dirección de memoria, que puede ser la dirección de
memoria de otra variable, por tanto, podemos decir que el puntero apunta a otra
variable. La variable apuntada puede ser de cualquier tipo de dato básico
(char, int, float, double), derivado (puntero) o estructurado (tabla,
estructura, etc..).
Los punteros tiene una gran
utilidad, pues permiten una programación eficaz a nivel de máquina cuando se
maneja la memoria del ordenador.
Su uso en C proporciona las
siguientes ventajas:
·
En el caso de paso de variables a
una función por dirección o referencia, es necesario emplear parámetros
formales como punteros.
·
Permiten realizar operaciones de
asignación dinámica de memoria.
·
Permiten efectuar operaciones con
estructuras de datos dinámicas.
Los punteros deben usarse con
precaución, ya que pueden provocar fallos en el programa difíciles de
localizar, las causas más corrientes de error en el uso de punteros es:
·
Asignación incorrecta de
direcciones, por lo que se puede llegar a escribir en zonas de la memoria que
contengan código o datos del programa
·
Operaciones incorrectas con los
punteros, cuando no se tiene en cuenta el tipo de valor de las variables
apuntadas.
DEFINICION DE PUNTEROS Y
ASIGNACIÓN DE DIRECCIONES
a)
Definición de variables puntero:
Tipo_básico_de_dato *Nombre_variable_puntero;
Ejemplo:
Int *apunt;
Tipo_básico_de_dato
nombre_pnt1, punt2, ...
Ejemplo:
Flota *pnum, *pprec, *pimp;
b)
Asignación de
direcciones a punteros:
La forma de indicar la dirección
de una variable, es utilizando el operador &de tipo monario precediendo al
nombre de la variable, por tanto, hay que diferenciar en una variable, cuando
se está empleando su valor (referenciado por su nombre) o cuando se está
empleando su dirección (al preceder su nombre con el símbolo &).
Int cant;
Cant = 15;
&cant; /* Indica la dirección de la variable cant*/
Para que una
variable puntero apunte a otra variable es necesario asignar la dirección de
dicha variable al puntero. El tipo de dato del puntero debe coincidir con el
tipo de dato de la variable apuntada, excepto en el caso de un puntero genérico
(con el tipo de dato void). La asignación de la dirección se puede realizar de
dos formas:
a)
Por inicialización en la
definición del puntero
Float num;
Float *pnum = #
b)
Por una sentencia de asignación
después de haber definido el puntero.
Float nume, *pnum;
Pnum = &nume;
Para visualizar la dirección contenida en un puntero, se puede
utilizar el formato %p con la función printf()
Printf(“La dirección contenida en la variable es %p: “,pnum);
Después de
tener asignada una dirección a un puntero, durante la ejecución del programa se
le puede volver a asignar la dirección de otra variable, apuntando por tanto a
esa variable y abandonando el apuntamiento anterior.
Float nume, contador;
float *pnum =&nume;
pnum =&contador;
INDIRECCIÓN
Se entiende por indirección la forma de referenciar el valor de una
variable a través de un puntero que apunta a dicha variable. Para realizar la
indirección se utiliza el operador monario * que aplicado al nombre del puntero
correspondiente indica el valor de la variable cuya dirección está contenida en
dicho puntero.
*Nombre_variable_puntero;
int a = 40, b ; /*Definición de variables*/
int* num = &a; / * Definir una variable puntero*/
pnum = 568; /* Indica el valor de la variable apuntada */
b = *pnum; /* El valor de la variable apuntada se asigna a otra
variable*/
int a= 5; b;
int *pnum = &a;
b = pnum + 1; / * se le asigna el valor de a + 1, */
b = *(pnum + 1) / * a la variable b se le asigna el valor contenido en
la dirección de memoria obtenida por la operación aritmética. */
Int a = 5, b, c;
Int *p1 =&a, *p2;
P2 = p1;
B = *p1;
c = *p2;
ARITMÉTICA DE PUNTEROS
·
Sumar o restar un número entero a un puntero.
Punt =#
Pnum =punt + 2
/ * Si se se supone que la dirección contenida
en punt es 4274, la dirección que contiene ‘pnum’ es 4274 + 2 * 4 = 4282 * /
Char 1 byte
Int 2 bytes
Long int 4 bytes
Float 4 bytes
·
Incremento o decremento de punteros
Float *apunt;
Apunt++;
apunt++ /*
Incrementa la dirección de apunt y después obtiene el resultado * /
(*apunt)++ / *
Incrementa en 1 el valor apuntado por apunt*/
·
Resta de dos punteros.
int tabula[10],
d;
int *ptab1 =
&tabula[1];
int *ptab2 =
&tabula[4];
d = ptab1 –
ptab2;
d contiene el
número de elementos separados por los punteros
COMPARACIÓN DE PUNTEROS
Se utiliza normalmente para la
comparación de punteros para conocer las posiciones relativas en memoria que
ocupan las variables apuntadas por punteros.
Int *p1 =
#
Int *p2 =
&cant;
Si efectuamos la comparación p1 > p2 podemos saber qué variable ocupa
mayor posición de memoria. Hay que evitar la confusión de comparar valores.
*p1 > *p2 Compara los valores de las variables
apuntadas.
PUNTEROS Y ARRAYS
En el lenguaje C los punteros y
los arrays están estrechamente relacionados. Para facilitar su estudio vamos a
diferenciar los casos de arrays unidimensionales (vectores) cadenas de
caracteres y arrays multidimensionales.
Punteros y arrays
unidimensionales.
El nombre del array (sin índice)
es un puntero que contiene la dirección de memoria del primer elemento del
array. El nombre del array es una constante de tipo puntero y por tanto el
compilador no permitirá que se pueda
cambiar en las instrucciones del programa la dirección contenida en el nombre
del array.
Int tabula[20];
El empleo de
tabula equivale a &tabula[0]
Tabula++; es una
operación incorrecta, pues ‘tabula’ es una constante.
Se pueden utilizar variables
puntero que contengan la dirección de un array, lo que posibilita la realización
de todas aquellas operaciones permitidas con punteros.
Ejemplo:
Carga la dirección del primer elemento
Int *ptab = tabula; equivale a int *ptab = &tabula[0];
Incrementa el puntero ‘ptab’ (apunta al segundo elemento)
Ptab++
‘ptab’ avanza cuatro elementos (apunta al sexto elemento)
ptab = ptab + 4;
La
referencia al valor de un elemento del array se puede hacer por medio de la
indirección de un puntero.
Int tabula[30], num;
Int *ptab = tabula;
Num = *(ptab + 4);
equivale a num=tabula[4];
Para
leer un elemento de un array:
Scanf( “ %d ”, ptab +
6); /* equivale a scanf ( “%d”,
&tabula[6] ) */
Para
escribir un elemento del array:
Printf( “ %d” , *(ptab +
6 ) ); “equivale a printf(“%d”,
tabula[6]); * /
Una
vez definido un puntero, se puede indexar como se hace con el nombre de un
array.
Int *ptab = tabula
Ptab[0] equivale a
*ptab
Ptab[8] equivale a
*(ptab + 8)
Esta
indexación de punteros sólo es válida cuando se utiliza para apuntar a arrays,
siendo errónea en los demás casos.
Int a, b;
Int *punt = &a;
B = punt[5];
Sentencia errónea.
También
es posible utilizar el nombre de un array como un puntero para referenciar a
los elementos del array.
Int vector[40];
*(vector + 3) es equivalente [3]
La diferencia entre el uso del nombre del array como puntero
y el uso de un puntero definido específicamente para contener la dirección del
array es que el primero es una constante y siempre debe apuntar a la misma
dirección de memoria (posición del primer elemento del array), mientras que el
segundo es una variable que puede cambiar su contenido o sea que puede contener
la direcciónde cualquier elemento del array, incluso se puede volver a utilizar
la variable puntero para apuntar a otro array distinto, siempre que sus
elementos sean del mismo tipo que el definido para el puntero.
Int num[20], cant[8];
Int +*punt =num;
Punt = cant;
El
uso de punteros, a pesar de ser más difícil presenta las siguientes ventajas:
·
Mayor rapidez en la ejecucióndel programa
·
Utilizan menos memoria
INDIRECCIÓN MÚLTIPLE
La indirección múltiple consiste en que un puntero contiene
la dirección de otro puntero que a su vez apunta a una variable. Su formato de
definición es el siguiente:
Tipo_dato
**Nombre_puntero;
Int cant;
Int *pcan =&cant;
Int **ppcan = &pcan;
**ppcan=57;
printf(“%d”,cant);
Visualiza el 57
En
realidad se está utilizando un formato de un array bidimensional de punteros,
tal como se describia en el apartado anterior para arrays de punteros a
cadenas.
Char nom[35];
Char *pnom = nom;
Char **ppnom = &pnom;
Gets(*ppnom);
Printf(“%s”,nom);
La
indirección múltiple se puede extender a más niveles, sin embargo, no es
conveniente pasar del nivel ** de puntero a puntero, pues se presentan
dificultadoes de seguimiento en el programa.
FUNCIONES DE ASIGNACIÓN DINÁMICA DE MEMORIA
Hasta ahora solo hemos manejado estructuras de datos que se
caracterizan porque obligaban de antemano un tamaño y estructura para que en
tiempo de compilación se reservase en memoria el suficiente espacio para ellas
(estructura de datos estáticas), permaneciendo invariables a lo largo de la
ejecución del programa. Pero existe una segunda manera de almacenar y tratar la
información sobre la memoria principal y que es mediante la asignación dinámica
de memoria. Este segundo nivel nos da
opción a reservar el espacio que necesitemos en memoria durante la ejecución
del programa permitiéndonos posteriormente suliberación, dando la posibilidad
de que en otra parte del programa se use en otro momento la misma zona de
memoria con un cometido diferente.
Esta manera de utilizar la memoria la podemos repetir tantas
veces como sea necesario durante la ejecución de un programa, teniendo siempre
la precaución de ir liberando aquellas porciones o parcelas de memoria
reservadas anteriormente y que ya no vamos a volver a utilizar.
Las funciones de la libreria que el ANSI C define para las
operaciones de asignación dinámica de memoria son las descritas a continuación:
Void *calloc(size_t num, size_t tam);
Se encuentra en la libreria stdlib.h, devuelve un puntero al
primer byte de la parcela o porción de memoria reservada o un puntero NULL, en
el caso de no haberse podido reservar el bloque de memoria solicitado. Reserva
un bloque de memoria para almacenar ‘num’ elementos de ‘tam’ bytes cada uno de
ellos.
Los argumentos son necesarios para la utilización de
calloc() son;
·
num: indica el número de elementos con la misma estructura
que ocuparán la parcela de memoria reservada.
·
Tam:indica el tamaño en bytes de cada uno de los elementos
que van a ocupar la parcela de memoria reservada.
La cantidad de memoria reservada, viene determinada por el
resultado que se obtiene al multiplicar el número de elementos a almacenar en
el bloque por el tamaño en bytes de cada uno de esos elementos, es decir num*tam
Ejemplo:
#include <stdio.h>
#include <stdlib.h>
int *reserva_memoria(int num_element)
{
int *pt;
pt = (int
*) calloc (num_element, sizeof(int));
if (!pt)
{
printf(“Imposible
reservar memoria.\n”);
...
}
return pt;
}
Void *malloc(size_t tam);
Se
encuentra en la libreria stdlib.h, devuelve un puntero al primer byte de la
parcela o porción de memoria reservada o un puntero NULL, en caso de no haberse
podido reservar el bloque de memoria solicitado.
Los argumentos necesarios para la utilización de la función
malloc son:
·
tam: Indica el tamaño en bytes del bloque de memoria que se
desea reservar. Es muy importante comprobar que el puntero devuelto por
malloc() no es un puntero nulo antes de hacer uso de él.
Void free(void *puntero);
Se
encuentra en la libreria stdlib.h, no devuelve ningún valor. Libera la parcela
de memoria apuntada por el puntero y que previamente habia sido asignado
mediante malloc() o calloc(), dando la posibilidad a que dicho bloque de
memoria se pueda volver a asignar posteriormente.
Los
argumentos necesarios para la función free son():
*Puntero: variable puntero que debe conteener la dirección
de memoria del primer byte del bloque de memoria que queremos liberar.
#include <stdio.h>
#include <stdlib.h>
main()
{
char *nombres[50];
int j;
}
se reserva espacio de memoria de 50 nombres
for (j = 0 ; j
< 50; j + + )
{
if((nombres[j]
= (char *) malloc(45)) = NULL)
{
printf(“Memoria
insuficiente. \n”);
exit (0);
}
gets(nombres[j]);
}
/* Se libera la memoria ocupada */
for (j=0; j <
50 ; j + +)
{
free ( nombres[j]);
}
}
Void *realloc (void *ptr, size_t nuevotam);
Cambia
el tamaño del bloque de memoria apuntada por ‘ptr’ al nuevo tamaño indicado por
‘nuevotam’.
Los argumentos necesarios para la utilización de la función
realloc() son:
·
ptr: puntero que apunta al bloque de memoria reservado.
·
Nuevotam: Valor en bytes que indica el nuevo tamaño del
bloque de memoria apuntado por ‘ptr’ y que puede ser mayor o menor que el
tamaño original.
#include <stdio.h>
#include <stdlib.h>
main()
{
char
saludo[] = “Hola”;
char
nombre[] = “Juan”;
char *pt;
int i;
pt = (char
*) malloc (5);
if (!pt)
{
printf(“Memoria
insuficiente. \n”);
exit
(1);
}
pt = (char
*) saludo;
for (j = 0; j < 5;
j + +)
printf(“%c”,*(pt+j));
pt = (char *) realloc (pt, 10);
if(!pt)
{
printf(“Imposible
reservar memoria. \n”);
exit(1);
}
strcat(pt, nombre);
printf(“\%s”, pt);
free(pt);
}