Curso de programación PIC en C con CCS. Punteros (1)

Volver al índice

 

Empezamos hoy un tema muy importante en C como son los punteros, veremos que son, para que sirven y las precauciones que tenemos que tener en cuenta cuando los utilicemos.

¿Que es un puntero?

Un puntero es una variable más, pero que almacena la dirección de memoria de otra variable.

¿Para que sirven los punteros?

El conocer la dirección de memoria de una variable es muy útil en C, como funciones principales que tienen los punteros podemos citar las siguientes:

  • Pueden proporcionar una forma rápida de acceder o referenciar a tipos de dados compuestos como arrays, estructuras y enumeraciones.
  • Sirven para pasar variables por referencia a las funciones.
  • Según los casos pueden optimizar el código y ahorrar recursos de memoria.

¿De que operadores dispongo para manipular los punteros?

1. El primer operador de punteros es &, un operador monario que devuelve la dirección de memoria del operando. (Un operador monario es aquel que solo requiere un operando).

Por ejemplo:

m=&contador;

Coloca en m la dirección de memoria de la variable contador, o sea la dirección del registro del PIC donde se ha guardado la variable contador.

Para comprenderlo mejor, supongamos que la variable contador utiliza la posición de memoria 0x0C (primer registro de propósito general del banco 0 del PIC 16F84A) para guardar su valor, también supongamos que el valor de contador es 100. Después de la asignación, m tendrá el valor de 0x0C. Se puede pensar en & como "la dirección de". Por tanto, la sentencia anterior de asignación significa  "m recibe la dirección de contador".

2. El segundo operador de punteros es *, que es el complementario de &. Es otro operador monario que devuelve el valor de la variable ubicada en la dirección que se especifica. Por ejemplo, si m contiene la dirección de memoria de la variable contador, entonces:

q=*m;

Colocará el valor de contador en q. Siguiendo con el ejemplo, q tendrá el valor 100, ya que 100 es lo guardado en el registro 0x0C, que es la dirección de memoria que indica m.
Piensa en * como "valor en la dirección". En este caso, la sentencia de asignación significa que recibe el valor en la dirección m.
Como vemos y eso puede despistar un poco, el símbolo de estos operadores coincide con los operadores  (&) AND lógico y el operador matemático (*) Multiplicación.
No hay ningún problema en esto porque el compilador se encarga de diferenciarlos según el contexto donde estén colocados

3. El tercer operador utilizado es: -> y se utiliza para acceder a tipo de datos compuestos en C como las estructuras, lo veremos más adelante cuando veamos este tipo de datos.

¿Como se declara una variable puntero?

Las variables que vayan a contener direcciones de memoria, o punteros, como se llaman en C, deben declararse colocando un * delante del nombre de la variable. Esto indica al compilador que va a contener un puntero a ese tipo de variable. Por ejemplo, para declarar c como puntero a carácter (char) escribiremos lo siguiente:

char *c;

Aquí, c no es un carácter, sino un puntero a un carácter, el tipo de dato al que apunta un puntero, en este caso char, se denomina tipo base del puntero. Bueno puede que alguien se pregunte lo siguiente ¿de que tipo es la propia variable puntero? Pues esta claro que tiene que ser de un tipo de datos cuyo tamaño sea suficiente para guardar una dirección de memoria tal y como esté definida por la arquitectura del microcontrolador que se utilice. Por ejemplo si utilizamos el PIC 16f84A, con un tipo de dato entero de 8 bits (int8) sería suficiente para direccionar todos los registros de este PIC, pero ese tipo de datos sería insuficiente para direccionar toda la memoria de otro PIC con mayor memoria RAM. Lo que hace CCS es establecer como tipo de dato por defecto para los punteros el entero de 16 bits (int16 en el tipo de dato nativo de CCS) para que sea compatible con todos los PICs que actualmente se pueden programar con CCS, pero da la opción de modificar ese parámetro a través de la directiva #device:

Esta directiva se encuentra incluida en el archivo de cabecera y en la siguiente instrucción se define para que el compilador le asigne un tipo de dato int8 (1 byte)  al PIC 16f84A para las variables puntero.

#device PIC16f84A *=8


Bueno, es una manera de ahorrar recursos cuando utilicemos punteros en PICs con poca memoria RAM. Pero en la mayoría de los casos la opción por defecto será la correcta y además de no tener que preocuparnos por ello, ganamos portabilidad en nuestro código, al saber que es compatible para todos los PICs.

Lo que si es responsabilidad del programador en cada momento es el tener en cuenta que un puntero sólo debe ser usado para apuntar a datos que sean del tipo base del puntero declarado (este tipo de cosas se verá mas adelante cuando veamos las precauciones que tenemos que tener en cuenta cuando usemos punteros).

Nota: se puede mezclar en una misma sentencia la declaración de variables puntero con variables normales, por ejemplo.

int x, *y, z; //declara x, z como variables de tipo entero, e y como puntero a un tipo entero.

Vamos a ver un ejemplo del uso de punteros:

#include "16F84A.h" 
#use delay(clock=4000000) 
#fuses XT,NOWDT 
#use rs232(baud=9600,parity=N,xmit=PIN_B1,rcv=PIN_B2,bits=8) 
 
void main() { 
 
 int fuente, destino; 
 int *p; //declaración de p como un puntero de tipo entero 
 
 fuente=4;//asigno el valor 4 a la variable fuente 
 p=&fuente; //al puntero p se le asigna la dirección de memoria de la variable fuente 
 destino=*p;// Le asigno a la variable destino el valor de fuente a través del puntero p 
 
 //Mostramos los resultados en la terminal 
 printf("La variable fuente tiene la direccion: %x (hex)\r",&fuente); 
 printf("La variable destino tiene la direccion: %x (hex)\r",&destino); 
 printf("La variable puntero p tiene la direccion: %x (hex)\r",&p); 
 
 printf("La variable fuente tiene el valor de: %d\r",fuente); 
 printf("destino tiene el valor de: %d, asignacion hecha con el puntero p\r",*p); 
 
 //Muestro el numero de bytes que ocupa en memoria una variable puntero 
 printf("El tama\xa4o de la variable puntero es de: %d\n byte" , sizeof(p)); 
 
} 
 

La salida del programa  así como el valor de los registros del PiC se pueden visualizar si simulamos nuestro ejemplo en Proteus.

 

Simulación con Proteus

 

Comentario

En este ejemplo se ha modificado el tipo de dato asignado por defecto por el compilador en la declaración de las variables puntero. Para ello como he dicho ya, hay que modificar la directiva #device en el archivo de cabecera según se muestra en la figura de abajo:

 

Archivo de cabecera PIC16f84A

 

Podemos ver el tamaño que tiene la variable puntero por medio del operador sizeof(), si no modificáis el parámetro de la directiva #device veréis que el tamaño en ese caso es de 2 bytes.

 

Nota: cada vez que se modifique alguna librería perteneciente a CCS es conveniente hacer una copia de dicha librería en la carpeta de nuestro proyecto y modificarla allí, acordarse en este caso de incluir en el programa principal el archivo de cabecera entre comillas dobles y no entre los signos < >.

En la declaración de las variables el compilador reserva las posiciones de memoria RAM necesarias  para poder contener los datos de las variables declaradas, como los registros de la RAM de propósito general del PIC 16FXXX son de un byte (8 bits) de tamaño, las posiciones de memoria reservadas por el compilador dependerán del tipo de dato con el que se ha declarado la variable, por ejemplo una variable declarada como tipo entero (int8) necesita un solo registro para almacenar su valor, una variable declarada como tipo float necesitará 4 registros (32 bits) para almacenar su valor.

 

Diagrama 1

 

En el siguiente paso se produce la asignación de datos a la variable fuente y a la variable puntero p, como veis en la figura de abajo, el dato almacenado en p es la dirección de memoria de la variable fuente, a partir de aquí se suele decir que p apunta a la variable fuente y  que la variable puntero p queda vinculada a esa dirección de memoria.

 

Diagrama 2

 

Ahora hacemos una asignación indirecta de datos utilizando el operador de indirección  (*).  Como se ve en la figura de abajo  asignamos a la variable destino el valor que tiene la variable fuente indirectamente  a través del puntero p.

Diagrama 3

 

Bueno ahora tenía pensado hacer un ejemplo con los posibles errores que podemos cometer a la hora de utilizar punteros, pero por hoy lo tengo que dejar,  quizás en el próximo artículo.


P.D.: si veis algún error ó algo que aportar al respecto sería de agradecer que lo comentarais en el foro.


Un saludo y hasta pronto.

 

Volver al índice

© 2007-2017 AquiHayapuntes.com