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. */

 

 

OPERACIONES CON PUNTEROS

 

ASIGNACIÓN DE PUNTEROS

 

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.

Nombre_punter + n = Dirección + n *tamaño_tipo_dato

Float num, *punt, *pnum

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

Double 8 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);

 

Se encuentra en la libreria stdlib.h. Devuelve un puntero al nuevo bloque de memoria reservado o un puntero nulo (NULL) en el caso de que la operación no se realice con

éxito.

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);

}