You are on page 1of 16

CCS - Punteros Introduccin Programando en CCS.

Una de las caracteristicas mas interesantes de las diferentes versiones de C son los punteros. Por supuesto, CCS permite el manejo de punteros, con lo que nuestros progamas pueden aprovechar toda la potencia de esta herramienta. El presente artculo fue escrito por Pedro (PalitroqueZ), un amigo de uControl. Su direccin de correo elecrnico es palitroquez@gmail.com. Tabla de contenidos

1 Introduccin 2 Qu es un puntero? 3 Para que pueden servir los punteros? 4 Como funcionan los punteros? 5 Como podemos acceder a la direccin de una variable? 6 Probando punteros, primera parte 7 Probando punteros, segunda parte 8 Punteros en funciones 9 Punteros en Arrays o 9.1 Arreglando el programa con *(p+t) 10 Temas relacionados 11 Autor

Qu es un puntero? Un puntero es una variable cuya finalidad es almacenar nmeros ENTEROS POSITIVOS. Estos nmeros no son nmeros al azar, son direcciones de la memoria que posee el hardware del microcontrolador (memoria de programa o RAM). Para que pueden servir los punteros? Esta es la pregunta que puede alborotar a mas de un programador de C. Sirve para muchsimas cosas:

Acceso a la memoria RAM del PIC. Ahorrar memoria RAM. Modificar ms de una variable dentro de una funcin (y por consiguiente devolver mas de un valor) En arreglos y cadenas strings (arrays, matrices) juega un papel importantsimo. Permite crear tablas con montones de datos (en los PIC que soporten acceso a la memoria de programa). En un ordenador se ampla el abanico de opciones.

Ms abajo veremos detalladamente como hacer todo esto. Como funcionan los punteros? Para entender el uso de estas variables especiales hay que comprender bien un concepto: Cuando se crea una variable en CCS (llamado registro en ensamblador), el compilador reserva un espacio de memoria cuyo tamao varia de acuerdo al tipo de dato. Como todo en el mundo electrnico/digital, est basado en 2 cosas:

El registro: es la casilla donde se almacena el dato. La direccin del registro: es la posicin en la memoria donde est alojado el registro.

as pues tenemos 2 elementos diferentes pero que se relacionan. Conociendo la direccin del registro o variable y pudindolo manejar nos da un poderosa herramienta para agilizar/simplificar nuestros programas. Como podemos acceder a la direccin de una variable? En CCS se hace a travs del operador &. Veamos un ejemplo: Ejemplo1: #include <18F4550.h> #use delay(clock=4000000) void main(){ int t,k; t=5; k= &t; delay_cycles(1); } al simular en el MPLAB tenemos:

Cuando detenemos en delay_cycles(1) vemos que en k se guarda la direccin de la variable t, y que guarda t? guarda el nmero 5. todo se realiza usando memoria RAM el registro de propsito general GPR. Vamos a cambiar ligeramente el cdigo. Usemos 3 variables tipo entero (int): #include <18F4550.h> #use delay(clock=4000000) void main(){ int k,l,m; int t,u,v; t=0xfa; u=0xfb; v=0xfc; k= &t; l= &u; m= &v; delay_cycles(1); }

se repite lo mismo, el resultado de las direcciones en k, l y m son contiguas. Pero... por que? Para responder esta pregunta vamos a cambiar el cdigo otra vez, declarando los 3 tipos de registros conocidos, int, long y float: #include <18F4550.h> #use delay(clock=4000000) void main(){

int k,l,m,n; int t; long u; float v; int z; t=0xfa; z=0xff; u=0xfffa; v=3.45000000; k= &t; l= &u; m= &v; n=&z; delay_cycles(1); } la simulacin:

Observa que las direcciones de t, u y v saltan. Por que? Dependiendo del tipo de dato se consume >= 1 byte de memoria. En el caso de t es un entero, y los enteros ocupan 1 byte (0..255). u es un dato "entero largo", ocupa dos bytes (0..65535) v es un dato "coma flotante", con parte fraccionaria en el sistema decimal y toma 4 bytes de memoria (32 bits)

en t tenemos una direccin que ocupa un byte [0xA] en u tenemos una direccin que ocupa 2 byte [0xB - 0xC] en v tenemos una direccin que ocupa 4 bytes [0xD - 0x10]

Probando punteros, primera parte Siempre que se declare una variable puntero, al momento de usarlo se debe especificar la direccin de apuntamiento de la variable normal, porque entonces no se puede guardar un dato sino sabemos donde lo vamos a guardar. (Es obvio pero es cierto) Esto quiere decir que se le debe pasar el nmero por valor de la direccin de la variable normal. Recordemos que:

Pasar un dato por valor: se copia el dato de una variable a otra. Pasar un dato por referencia: se mueve/modifica el dato en la misma variable. Variable normal: la variable que normalmente usamos. Variable puntero: es la variable especial que estamos estudiando.

Veamos un ejemplo sencillo usando punteros: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ int k; // variable normal int *p; // la variable puntero k=0xfa; // k <- 0xfa *p=0x5; delay_cycles(1); }

Dentro del cdigo reconocemos de inmediato quien es el puntero: el que tiene el smbolo * debe ir antes de la letra p y sin separacin:

*p as es como se debe declarar. si nos vamos a MPLAB-SIM, y trazamos hasta delay_cycles(1) vemos en la ventana LOCAL:

pero... no aparece nada en p! Por que? Es simple: porque no fijamos una direccin que apuntara p, y esto es muy importante saberlo, era lo que se deca al inicio de este artculo. Vamos a darle la direccin de k: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ int k; // variable normal int *p; // la variable puntero p=&k; // direccin de k copiada a p k=0xfa; // k <- 0xfa *p=0x5; // k <- 0x5 delay_cycles(1); } el resultado:

Ahora si funciona correctamente el cdigo. Si ven la lnea: p=&k; // direccin de k copiada a p

Podran ovservar que se usa el puntero sin el *. Esto significa que se guardar all una direccin y el compilador lo interpreta de esa manera. Y con esta lnea: *p=0x5; // k <- 0x5

se est modificando el contenido de k, (indirectamente)

Otro detalle a tomar en cuenta es que para apuntar cierto tipos de datos, es que se debe declarar al apuntador con el mismo tipo de datos: int k; // si queremos apuntar a k int *p; // p debe ser tipo int char c; // si queremos apuntar a c char *p // p debe ser tipo char

no quiere decir que el tipo de datos que contendr el puntero sea de ese tipo de datos, el puntero siempre soportar nmeros enteros positivos, en realidad esto ya es a nivel interno del compilador. Un ejemplo ms: En este cdigo hay 2 punteros y 3 variables normales: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ int i; // variable normal int *p; // la variable puntero int j; int *q; int k; // p=&i; // direccin de i copiada a p q=&j; // i=0xfa; // i <- 0xfa j=0x11; k=0x22; // *p=0x5; // i <- 0x5 *q=0x33; delay_cycles(1); }

Entre i, p hay 1 byte -> i ocupa 1 byte. Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes. Entre j, q hay 1 byte -> j ocupa 1 byte Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.

Modificando el cdigo para que i sea del tipo float: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ float i; // variable normal float *p; // la variable puntero // long j; long *q; int k; // i=2.51; // i <- 0xfa j=0x11; k=0x22; // p=&i; // direccin de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1); }

Entre i, p hay 4 bytes -> i ocupa 4 bytes. Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes. Entre j, q hay 2 bytes -> j ocupa 2 bytes. Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.

En ambos casos a pesar que cambiamos el tipo de declaracin de los punteros, se mantienen en 2 bytes, eso quiere decir que para el compilador el tamao de un puntero es de 2 bytes. No confundir con el tipo de datos a direccionar, pues eso es otra cosa. Vamos con otro ejemplo. Supongamos que i sea del tipo float (4 bytes) pero su apuntador lo declaramos como int (1 byte): #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ float i; // variable normal int *p; // la variable puntero long j; long *q; int k; // i=2.51; // i <- 0xfa j=0x11; k=0x22; // p=&i; // direccin de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1); }

Noten que el nuevo valor de i no corresponde con el valor que indirectamente le dimos con el apuntador. Por que? Porque declaramos a ese apuntador como entero (int) y con ello le estamos diciendo al compilador que reserve para p 1 byte de direccin en vez de 4 bytes que son los que se necesitan y por eso ocurre ese truncamiento y da ese valor extrao.

Para corregir esto, se declara a p del MISMO tipo de dato de i: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ float i; // variable normal float *p; // la variable puntero long j; long *q; int k; // i=2.51; // i <- 0xfa j=0x11; k=0x22; // p=&i; // direccin de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1); }

Aqu se lee que est correcto el resultado.

Nota: los punteros tiene un mximo de 2 bytes para almacenar direcciones y el CCS sigue la misma normativa. Hay una directiva llamada #device xxxxxxx

con 4 modos de seleccin: CCS2,CCS3,CCS4 y ANSI. Con CCS2 y CCS3 el tamao (size) del puntero es de 1 byte en partes de 14, 16 bits y con CCS4 (modo por defecto) el size es de 2 bytes. Probando punteros, segunda parte Analizando nuevamente lo hablado referente al size de los punteros en CCS, y en un intento de explicar que el tipo de dato y el tamao del apuntado son 2 cosas distintas, vamos hacer un ejemplo donde se ver claramente. Para ello vamos a usar una directiva llamada #locate, sobre la que la ayuda del compilador reza as: #LOCATE works like #BYTE however in addition it prevents C from using the area bueno esto quiere decir que la variable normal la puedo alojar en cualquier direccin de la RAM (dentro de ciertos limites). Algo as como si en ensamblador pusieramos: variable_normal EQU 0xNNNN Esto nos servir porque sera como manipular el contenido de un puntero pero en tiempo de diseo

#include <18F4550.h> #use delay(clock=4000000) //********************************* int dato=0xaa; // declaramos dato (GPR) y lo cargamos con 0xAA #locate dato = 0xff // le decimos al compilador que dato estar en la direccin 0xFF //del rea de registro de propsito general, traducido, en la RAM del PIC void main(){ int *p; // declaramos un puntero como entero (igual que dato) int t; // otra variable normal p=&dato; // inicializamos al puntero

*p=0xbb; // dato <- 0xBB delay_cycles(1); // un nop }

Fjense que el puntero p ocupa 2 bytes a pesar que est declarado como int (1 byte). Vamos a modificar este ejemplo pero usando float (4 bytes) y colocando el GPR en la direccin 0xAF

Observen que el puntero p se mantuvo en 2 bytes siendo ste declarado como float. Supongamos un ejemplo para el PIC18F4550, en el que tenemos una memoria de datos que llega hasta 0x7FF (Pg. 66 de su hoja de datos). Para que funcione 0x7FF debe ser el 4 byte para un float, entonces

float dato=1.23456789; #locate dato = 0x7FB ...

Si que funcion. Pero, que pasa si asignamos el dato a 0x800?

All vemos que el puntero se carg bien, pero el MPLAB-SIM delata el desbordamiento, Por que? Es que a partir de all no hay memoria de datos y las direcciones se deberan leer como puros 0x0 a pesar que compil bien, (similarmente en programas de computadoras pueden ocurrir los lazos infinitos popularmente llamado se colg la mquina) [editar] Punteros en funciones Todo lo que hagamos en CCS se hace a travs de funciones o procedimientos, desde el punto de vista matemtico una funcin se define as: Una funcin es una relacin entre dos variables numricas, habitualmente las denominamos x e y; a una de ellas la llamamos variable dependiente pues depende de los valores de la otra para su valor, suele ser la y; a la otra por tanto se la denomina variable independiente y suele ser la x. Pero adems, para que una relacin sea funcin, a cada valor de la variable independiente le corresponde uno o ningn valor de la variable dependiente, no le pueden corresponder dos o ms valores. Aplicndolo a la programacin, significa que podemos tener varios argumentos o parmetros de entrada, pero solo tendremos un dato de salida. Y eso no es todo, en C una funcin pasa los argumentos por valor. que quiere decir esto? Que cuando llamemos a la funcin y le pasemos el dato como argumento, sta copiar ese dato en su propia funcin sin alterar la variable original. veamos un ejemplo:

#include <18F4550.h> #use delay(clock=4000000) //********************************* int mi_funcion(int argumento1, argumento2){ delay_cycles(1); return (argumento1 + argumento2); } //******************************* void main(){ int k,l,resultado; k=5; L=2; resultado = mi_funcion(k,L); delay_cycles(1); }

Noten que cuando llamo a mi_funcion, se copia el contenido de k -> argumento1 y L -> argumento2 , luego hace la suma y regresa un dato con el resultado de la suma. k y L se quedan con el mismo valor anterior.

y si queremos cambiar esas variables como se hace? Bueno seguro que alguien llegar y colocar a k y L como globales y entonces as se puede modificar en cualquier lado. Pero si la variable es local, dentro de main(), no se puede modificar fuera de main()...a menos que usemos punteros. y como se hara eso? Simple: se hara pasando el argumento a la funcin como referencia, haciendo referencia a la direccin, es decir lo que se pasar a la funcin es la direccin de k, L entonces all si se puede modificar a gusto. Un ejemplo: #include <18F4550.h> #use delay(clock=4000000) //********************************* int mi_funcion(int argumento1, argumento2, *la_k, *la_L){ delay_cycles(1); *la_k=0xFF; *la_L=0xAF; return (argumento1 + argumento2); } //******************************* void main(){ int k,l,resultado; k=5; l=2; resultado = mi_funcion(k,l,&k,&l); delay_cycles(1); } Punteros en Arrays Como sabrn los arrays son arreglos de datos que van en direcciones consecutivas, es decir, uno detrs del otro. Un ejemplo de ello: char cadena[7]={'T','o','d','o','P','i','c'}; Si lo probamos en un cdigo: #include <18F4550.h> #use delay(clock=4000000) //********************************* char cadena[7]={'T','o','d','o','P','i','c'}; void main(){ char c; int t; for(t=0;t<7;t++){ c=cadena[t]; } delay_cycles(1); }

Se pueden usar punteros en el ejemplo anterior. Veamos como:

Declarando un puntero como char:

char c, *p; lo inicializamos (le damos la direccin del primer elemento del array): p=&cadena[0]; luego hacemos un barrido de direcciones para tomar el contenido de cada elemento y guardarlo en c for(t=0;t<7;t++){ c= *p + t; } Pero ese programa tiene 2 errores y no funcionar: El primer error es que segn la precedencia del operador primero est el puntero y luego viene la suma, y as estaramos sumando direcciones que varan, la solucin es usar *(p+i) Y que es eso de que varan? Pues que cuando se recorre el arrays con el puntero, este debe ir sumando direcciones, pero direcciones de nmeros constantes, es decir, si el tipo de datos es 1 byte, entonces el puntero debe acumular nmeros enteros de 1 byte en 1 byte Si el tipo de datos es long (entero largo) entonces el puntero debe ir sumando direcciones de 2 bytes en 2 bytes. Porque digo esto? Es que p quedar fijo (la direccin) y el truco est en desplazar al puntero tantas posiciones sea el size del tipo de dato. Este sera el segundo error y la solucin es la misma: *(p+i) Vamos a cambiar ese ejemplo por nmeros long para que se entienda #include <18F4550.h> #use delay(clock=4000000) //********************************* long cadena[7]={1000,2000,3000,4000,5000,6000,7000}; void main(){ long c, *p; int t; p=&cadena[0]; for(t=0;t<7;t++){ c= *p + t; } delay_cycles(1); }

Fjense que p queda inmutable, y lo que hace el programa es contenido[0] + t. Grave error! [editar] Arreglando el programa con *(p+t)

Con esto estamos garantizando que el puntero se mover de 2 bytes en 2 bytes, es decir *(p+t) = 0x5 0x5 0x5 0x5 ... + + + + 0x2 (desplazamiento de 2 byte)-> dame el contenido 1x2 " -> dame el contenido 2x2 " -> dame el contenido 3x2 " -> dame el contenido de de de de la la la la direccin direccin direccin direccin 0x5 0x7 0x9 0xA

Noten que la suma se realiza no intervalos de t sino en intervalos del ancho del tipo de dato. Pero...esto no es lo mismo que se hizo en el cdigo del inicio del artculo? O sea que c = cadena[t]; es igual a c = *(p + t) cuando p = &cadena[0]; ? Pues si, acabamos de ver un array al desnudo, como funciona en realidad, no es mas que un puntero escondido a nuestra vista. Solo que para hacer fcil la programacin el compilador lo acepta de esta manera. Si p es un puntero -> p = cadena (para el primer ndice del arreglo) es totalmente vlido, se acepta que cadena es un puntero constante, tambin se podra llamar un puntero nulo (ya que no se ve y tampoco se puede modificar). Ejemplos validos: cadena[0] = *cadena cadena[2] = *(cadena + 2) cadena = *(cadena + i)

Nota: el operador () es el primero que atiende el compilador, antes que al resto. Acomodando el cdigo original, el que tena la cadena de caracteres:

#include <18F4550.h> #use delay(clock=4000000) //********************************* char cadena[7]={'T','o','d','o','P','i','c'}; void main(){ char c, *p; int t; p=cadena; for(t=0;t<7;t++){ c=*(p+t); } delay_cycles(1); }

You might also like