Punteros en Ccs

16
CCS - Punteros Introducción 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 artículo fue escrito por Pedro (PalitroqueZ), un amigo de uControl. Su dirección de correo elecrónico es [email protected] . Tabla de contenidos 1 Introducción 2 ¿Qué es un puntero? 3 ¿Para que pueden servir los punteros? 4 ¿Como funcionan los punteros? 5 ¿Como podemos acceder a la dirección 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 números ENTEROS POSITIVOS. Estos números no son números 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 muchísimas cosas: Acceso a la memoria RAM del PIC . Ahorrar memoria RAM. Modificar más de una variable dentro de una función (y por consiguiente devolver mas de un valor) En arreglos y cadenas strings (arrays, matrices) juega un papel importantísimo. Permite crear tablas con montones de datos (en los PIC que soporten acceso a la memoria de programa). En un ordenador se amplía el abanico de opciones. Más 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 tamaño varia de acuerdo al tipo de dato. Como todo en el mundo electrónico/digital, está basado en 2 cosas:

Transcript of Punteros en Ccs

Page 1: Punteros en Ccs

CCS - Punteros

Introducción

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 herramie nta.

El presente artículo fue escrito por Pedro (PalitroqueZ), un amigo de uControl . Su dirección de correo elecrónico es [email protected] .

Tabla de contenidos

• 1 Introducción

• 2 ¿Qué es un puntero?

• 3 ¿Para que pueden servir los punteros?

• 4 ¿Como funcionan los punteros?

• 5 ¿Como podemos acceder a la dirección de una varia ble?

• 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 números ENTEROS POSITI VOS. Estos números no son números al azar, son direcciones de la memoria que posee el hardware d el 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 muchísimas cosas:

• Acceso a la memoria RAM del PIC .

• Ahorrar memoria RAM.

• Modificar más de una variable dentro de una función (y por consiguiente devolver mas de un valor)

• En arreglos y cadenas strings (arrays, matrices) ju ega un papel importantísimo.

• Permite crear tablas con montones de datos (en los PIC que soporten acceso a la memoria de programa).

• En un ordenador se amplía el abanico de opciones.

Más abajo veremos detalladamente como hacer todo es to.

¿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 tamaño varia de acuerdo al tipo de dato.

Como todo en el mundo electrónico/digital, está bas ado en 2 cosas:

Page 2: Punteros en Ccs

• El registro : es la casilla donde se almacena el dato.

• La dirección del registro : es la posición en la memoria donde está alojado e l registro.

así pues tenemos 2 elementos diferentes pero que se relacionan.

Conociendo la dirección del registro o variable y p udiéndolo manejar nos da un poderosa herramienta para agilizar/simplificar nuestros prog ramas.

¿Como podemos acceder a la dirección de una variabl e?

En CCS se hace a través 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 dirección de la variable t , ¿y que guarda t ? guarda el número 5. todo se realiza usando memoria RAM ó el registro de propósito general GPR.

Vamos a cambiar ligeramente el código. Usemos 3 var iables 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 cód igo otra vez, declarando los 3 tipos de registros conocidos, int , long y float :

#include <18F4550.h> #use delay(clock=4000000) void main(){

Page 3: Punteros en Ccs

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 simulación:

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 dirección que ocupa un byte [0xA]

• en u tenemos una dirección que ocupa 2 byte [0xB - 0xC]

• en v tenemos una dirección que ocupa 4 bytes [0xD - 0x1 0]

Probando punteros, primera parte

Siempre que se declare una variable puntero, al mom ento de usarlo se debe especificar la dirección de apuntamiento de la variable normal, po rque entonces no se puede guardar un dato sino sabemos donde lo vamos a guardar. (Es obv io pero es cierto)

Esto quiere decir que se le debe pasar el número por valor de la dirección de la variable normal. Recordemos que:

• Pasar un dato por valor: se copia el dato de una va riable 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 estam os 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 código reconocemos de inmediato quien es el puntero: el que tiene el símbolo * debe ir antes de la letra p y sin separación:

Page 4: Punteros en Ccs

*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 dirección que apun tara p, y esto es muy importante saberlo, era lo que se decía al inicio de este artí culo. Vamos a darle la dirección de k :

#include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ int k; // variable normal int *p; // la variable puntero p=&k; // dirección de k copiada a p k=0xfa; // k <- 0xfa *p=0x5; // k <- 0x5 delay_cycles(1); }

el resultado:

Ahora si funciona correctamente el código. Si ven l a línea:

p=&k; // dirección de k copiada a p

Podran ovservar que se usa el puntero sin el * . Esto significa que se guardará allí una dirección y el compilador lo interpreta de esa mane ra.

Y con esta línea:

*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

Page 5: Punteros en Ccs

no quiere decir que el tipo de datos que contendrá el puntero sea de ese tipo de datos, el puntero siempre soportará números enteros positi vos, en realidad esto ya es a nivel interno del compilador.

Un ejemplo más:

En este código hay 2 punteros y 3 variables normale s:

#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; // dirección 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 código 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; // dirección de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1);

}

Page 6: Punteros en Ccs

• 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 dec laración de los punteros, se mantienen en 2 bytes, eso quiere decir que para el compilador el tamaño de un puntero es de 2 bytes. No confundir con el tipo de datos a dir eccionar, 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; // dirección 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 en tero ( int ) y con ello le estamos diciendo al compilador que reserve para p 1 byte de dirección en vez de 4 bytes que son los que se necesitan y por eso ocurre ese truncamie nto y da ese valor extraño.

Page 7: Punteros en Ccs

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; // dirección 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.

Page 8: Punteros en Ccs

Nota: los punteros tiene un máximo de 2 bytes para almace nar direcciones y el CCS sigue la misma normativa. Hay una directiva llamada #device xxxxxxx

con 4 modos de selección: CCS2, CCS3, CCS4 y ANSI. Con CCS2 y CCS3 el tamaño (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 tamaño del apuntado son 2 cosas distintas, vamos hacer un ejemplo donde se verá claramente. Pa ra 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 pre vents C from using the area

bueno esto quiere decir que la variable normal la p uedo alojar en cualquier dirección de la RAM (dentro de ciertos limites). Algo así como s i en ensamblador pusieramos:

variable_normal EQU 0xNNNN

Esto nos servirá porque sería como manipular el con tenido de un puntero pero en tiempo de diseño

#include <18F4550.h> #use delay(clock=4000000) //********************************* int dato=0xaa; // declaramos dato (GPR) y l o cargamos con 0xAA #locate dato = 0xff // le decimos al compilador que dato estará en la d irección 0xFF //del área de registro de propósito general, traduc ido, en la RAM del PIC void main(){ int *p; // declaramos un puntero como entero (ig ual que dato) int t; // otra variable normal p=&dato; // inicializamos al puntero

Page 9: Punteros en Ccs

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

Fíjense 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 dirección 0xAF

Page 10: Punteros en Ccs

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 (Pág. 66 de su hoja de datos). Pa ra 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 da to a 0x800?

Page 11: Punteros en Ccs

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 deberían leer como puros 0x0 a pesar que compiló bien, (similarmente en program as de computadoras pueden ocurrir los lazos infinitos popularmente lla mado ‘se colgó la máquina’)

[ editar ] Punteros en funciones

Todo lo que hagamos en CCS se hace a través de funciones o procedimientos, desde el punto de vista matemático una función se define así :

Una función es una relación entre dos variables num éricas, habitualmente las denominamos x e y; a una de ellas la llamamos variable dependie nte pues depende de los valores de la otra para su valor, suele ser la y; a la otra por t anto se la denomina variable independiente y suele ser la x. Pero además, para que una relación sea función, a cada valor de la variable independiente le corresponde u no o ningún valor de la variable dependiente, no le pueden corresponder dos o más va lores.

Aplicándolo a la programación, significa que podemo s tener varios argumentos o parámetros de entrada, pero solo tendremos un dato de salida. Y eso no es todo, en C una función pasa los argumentos por valor. ¿que quiere decir esto? Que cuando llamemos a la función y le pasemos el dato como argumento, ésta c opiará ese dato en su propia función 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 resu ltado de la suma. k y L se quedan con el mismo valor anterior.

Page 12: Punteros en Ccs

¿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 varia ble es local, dentro de main() , no se puede modificar fuera de main() ...a menos que usemos punteros. ¿y como se haría es o?

Simple: se haría pasando el argumento a la función como referencia, haciendo referencia a la dirección, es decir lo que se pasará a la func ión es la dirección 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 sabrán los arrays son arreglos de datos que van en direcciones consecutivas, es decir, uno detrás del otro. Un ejemplo de ello:

char cadena[7]={'T','o','d','o','P','i','c'};

Si lo probamos en un código:

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

Page 13: Punteros en Ccs

Se pueden usar punteros en el ejemplo anterior. Vea mos como:

• Declarando un puntero como char :

char c, *p;

lo inicializamos (le damos la dirección 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 según la precedencia del ope rador primero está el puntero y luego viene la suma, y así estaríamos sumando direcciones que varían, la solución es usar *(p+i )

¿Y que es eso de que varían? Pues que cuando se rec orre el arrays con el puntero, este debe ir sumando direcciones, pero direcciones de nú meros constantes, es decir, si el tipo de datos es 1 byte, entonces el puntero debe a cumular números 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 est o? Es que p quedará fijo (la dirección) y el truco está en desplazar al puntero tantas posiciones sea el size del tipo de dato. Este sería el segundo error y la solu ción es la misma: *(p+i)

Vamos a cambiar ese ejemplo por números 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); }

Page 14: Punteros en Ccs

Fíjense que p queda inmutable, y lo que hace el programa es contenido[0] + t . ¡Grave error!

[ editar ] Arreglando el programa con *(p+t)

Page 15: Punteros en Ccs

Con esto estamos garantizando que el puntero se mov erá de 2 bytes en 2 bytes, es decir

*(p+t) =

0x5 + 0x2 (desplazamiento de 2 byte)-> dame el cont enido de la dirección 0x5 0x5 + 1x2 " -> dame el cont enido de la dirección 0x7 0x5 + 2x2 " -> dame el cont enido de la dirección 0x9 0x5 + 3x2 " -> dame el cont enido de la dirección 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 código del inicio del artículo?

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 fácil la programación el compilador lo acepta de esta manera. Si p es un puntero -> p = cadena (para el primer índice del arreglo) es totalmente válido, se acepta que cadena es un puntero constante, también se podría 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 compil ador, antes que al resto.

Acomodando el código original, el que tenía la cade na de caracteres:

Page 16: Punteros en Ccs

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