Programación en C para sistemas basados en un microprocesador Una introducción
Objetivos Recordar los conceptos básicos de un lenguaje de alto nivel Comprender sencillos programas escritos en el lenguaje más empleado: “C” Comparar las características de “C” con las de Java y el lenguaje ensamblador.
Lenguajes de alto nivel Basados en el uso de compiladores o intérpretes Incrementan la productividad (respecto al ensamblador) Estructuras elegantes de control Complejas estructuras de datos Mayor abstracción: Manejo de la pila: funciones Memoria física: variables y tipos Incrementan la seguridad Programación estructurada (sin branches) Control de tipos (coherencia de tamaños y signos) Incrementan la portabilidad (multimáquina) Se dispone de más bibliotecas que facilitan la programación
¿Por qué “C”? Permite un buen control de los recursos de bajo nivel (aunque no tanto como el ensamblador) Permite la programación estructurada Es eficiente en velocidad y tamaño (aunque no tanto como un buen programa de un buen programador en ensamblador) Su uso está muy extendido en programación de sistemas (Linux, Windows, drivers...) Defecto: es fácil cometer errores: No está fuertemente tipado ni es orientado a objetos Permite crear código críptico Es difícil de dominar con maestría
Traducción a ensamblador 0200fc linkw %fp,#-4 020100 nop 020102 clrl %fp@(-4) 020106 moveq #9,%d0 020108 cmpl %fp@(-4),%d0 02010c bges 00020110 02010e bras 00020138 020110 moveal %fp@(8),%a0 020114 addal %fp@(-4),%a0 020118 moveal %fp@(12),%a1 02011c addal %fp@(-4),%a1 020120 moveb %a0@,%d0 020122 extbl %d0 020124 moveb %a1@,%d1 020126 extbl %d1 020128 cmpl %d0,%d1 02012a beqs 00020130 02012c moveq #1,%d0 02012e bras 0002013e 020130 moveq #1,%d0 020132 addl %d0,%fp@(-4) 020136 bras 00020106 int stringCmp(char s1[10],char s2[10]) { int i; for (i=0; i<10; i++) { if (s1[i]!=s2[i]) return 1; } return 0; Variables Locales: seguridad, abstrae memoria y pila Bucle: cómodo y estructurado Escribir cómodamente expresiones complejas Esta función compara 2 strings de 10 caracteres cada uno: devuelve 0 si son iguales, o 1 si son diferentes.En C se escribe menos código, es más legible (y mantenible), no nos tenemos que preocupar del manejo de la pila para el paso de parámetros y variables locales, verifica que las comparaciones se realizan entre elementos del mismo tipo (tamaño y signo), Manejo transparente de pila y registros Parameter s1 is at 8(A7) Parameter s2 is at 12(A7) Variable i is at -4(A7) Return value through -4(A7)
Proceso de compilación Main.asg main.o AS Fichero.c Fichero.s Fichero.o GCC AS LD Fichero.dep Fichero.hcf Fichero.elf OBJDUMP
“C” versus Java “C” no es un lenguaje orientado a objetos: No hay clases, objetos, métodos No hay herencia, polimorfismo (no puede haber 2 funciones con el mismo nombre) No hay objetos sino variables No hay métodos de una clase sino funciones No hay excepciones “C” no es un lenguaje interpretado No es “machine-independent”
Puntos comunes con Java (I) Bloques definidos por llaves El ámbito de una variable es el bloque más pequeño que contiene su declaración (salvo para los argumentos de una función) Comentarios: /* ... */ Tipos comunes: Los básicos: void, char, int, float Los modificadores long, short, double, signed y unsigned Definición de nuevos tipos mediante typedef Identificadores: case-sensitive, no deben empezar por un número. Ej: linea5, linea_5, linea_anterior
Puntos comunes con Java (II) Expresiones comunes: Los paréntesis son los elementos más prioritarios, seguidos por los operadores unitarios y por los binarios Operadores aritméticos: + - * / % ++ -- += -= *= /= I+=5+(5%2); /* I=i+6*/ Operadores lógicos y relacionales: && || ! < <= > >= == != if ((i>0 && i<10) || (j==0) ) { } Operadores de bit y de desplazamiento: & | >> << I=0xf0 & 0x0f; /* I=0 */ I=j<<3; /* ASL de 3 bits*/
Puntos comunes con Java (III) Bucles for (inicialización; condición; iteración) for (i=0; i<10; i++) { } for (int i=0;...) /* ERROR */ while (condición) while (i<10) { i++; } Condiciones: if (<cond>) {} else if (<cond>) {} else { } if (i<0) { } else if (i<10) { } else { } switch (variable) { case valor: {} break; default: {} break;}
Tipos enteros : tamaños El tamaño es dependiente de máquina y de compilador, aunque char es siempre de 8 bits En el ColdFire, con el compilador GCC: short int, signed short int, unsigned short int : 16 bits int, signed int, long int, unsigned long int, signed long int: 32 bits Las variables globales no son inicializadas nunca en el C del EdColdFire!!
Variables (asm vs. C) i=0; shi=1; li=2; si=0; sshi=1; sli=2; ui=0; int i; short int shi=1; long int li=2; signed int si=0; signed short int sshi=1; signed long int sli=2; unsigned int ui=0; unsigned short int ushi=1; unsigned long int uli=2; i=0; shi=1; li=2; si=0; sshi=1; sli=2; ui=0; ushi=1; uli=2; 00030000 00000002 D shi 00030002 00000004 D li 00030006 00000004 D si 0003000a 00000002 D sshi 0003000c 00000004 D sli 00030010 00000004 D ui 00030014 00000002 D ushi 00030016 00000004 D uli 0003001c 00000004 B i 00020100 clrl 0003001c <i> 00020106 moveq #1,%d0 00020108 movew %d0,00030000 <shi> 0002010e moveq #2,%d0 00020110 movel %d0,00030002 <li> 00020116 clrl 00030006 <si> 0002011c moveq #1,%d0 0002011e movew %d0,0003000a <sshi> 00020124 moveq #2,%d0 00020126 movel %d0,0003000c <sli> 0002012c clrl 00030010 <ui> 00020132 moveq #1,%d0 00020134 movew %d0,00030014 <ushi> 0002013a moveq #2,%d0 0002013c movel %d0,00030016 <uli>
Tipos enteros : tamaños y conversiones Conversiones implícitas: Al asignar una variable de un tipo mayor a una variable de tipo menor, se recorta (bits menos significativos) int i=0xfedbca56; short int si=i; /* Equivale a si=0xffff */ Al asignar una variable más pequeña a una mayor, no hay problema Al asignar entre variables de igual tamaño y distinto signo, no se pierden bits, pero puede variar su interpretación signed int si=-1; unsigned int ui; ui=si; /* ui=65535 */
Punteros (I) Variables que contienen una dirección de memoria Como los registros de direcciones Ax: Permiten acceder indirectamente a otras variables int i=0; /* La variable i contiene un 0 */ int *pi; /* declaración de un puntero */ pi=&i; /* El puntero pi contiene la dirección de la variable i */ *pi=2; /* La variable i ahora contiene un 2, el puntero pi no se ve alterado */ i=*pi; /* la variable i sigue conteniendo un 2 */ 2 i $XXX i $XXX $XXX $YYY pi pi $YYY $YYY
Punteros (II) También permiten acceder a posiciones del mapa de memoria de entrada y salida #define BASE_PUERTO_S 0x40000000 /* Direccion del puerto S */ unsigned char *puertoS=BASE_PUERTO_S; /* El puntero pi contiene la dirección del puerto de salida */ *puertoS=0xff; /* Se envía un $FF al puerto de salida */ En EdColdFire hay funciones para esta labor: Internamente usan punteros (ver m5272gpio.c) void set16_puertoS (UWORD valor) UWORD lee16_puertoE (void) $FF $XX $40000000 $40000000 $40000000 $40000000 puertoS puertoS $YYY $YYY Valor de la variable Posición en memoria Valor de la variable Posición en memoria variable variable
Punteros (III) Son un mecanismo de bajo nivel, peligroso Si no se les da un valor adecuado, pueden acceder a posiciones de memoria no deseadas: int *pi; Este es un puntero que apunta a un lugar indeterminado, por no estar inicializado *pi=3; Como pi puede apuntar a cualquier sitio, podemos estar escribiendo en cualquier punto del mapa de memoria, o incluso fuera de él, (provocando un error de bus o de dirección)
Arrays (I) Los array son punteros constantes (no variables) que permiten acceder, de una manera indexada, a una zona de memoria reservada por el compilador automáticamente Dicho puntero constante equivale a la dirección de comienzo del array Por ser una constante, este puntero no se inicializa; se inicializa el contenido de la zona de memoria apuntada El índice del primer elemento es el 0, el del segundo elemento es el 1, etc. Es posible acceder (por error o intencionadamente) a posiciones más allá del tamaño reservado para el array Para copiar un array en otro es necesario copiar elemento a elemento con un bucle
Arrays (II) int lista[3]={0,1,2}; int i=3; lista[1]=lista[2]; El array lista contiene 3 números, la variable i contiene un 3 lista[1]=lista[2]; Copia el valor de la posición 2 (que es la última), en la posición 1 (que es la segunda) lista[i]=0; ERROR: modifica la variable i (situada en memoria tras el array) $XXX array $XXX array 1 $XXX+4 2 $XXX+4 2 $XXX+8 2 $XXX+8 i 3 $XXX+12 i $XXX+12
Arrays (III) Los arrays de caracteres se suelen llamar strings. char asignatura[4]=“SED”; asignatura[0]=‘T’; Su tamaño debe ser “la longitud máxima de la cadena de caracteres” + 1, porque su último elemento es un 0 (no el carácter ‘0’). ‘S’ $XXX string ‘T’ $XXX string ‘E’ $XXX+1 ‘E’ $XXX+1 ‘D’ $XXX+2 ‘D’ $XXX+2 $XXX+3 $XXX+3
Funciones (I) Permiten dividir un problema en subproblemas Se implementan como subrutinas El manejo de la pila y el uso de los registros es automático e invisible al programador Admiten argumentos de entrada y de salida Suelen devolver un valor, pero no es obligatorio Si la función no devuelve nada, al llegar a la última llave, realiza el return automáticamente Todo programa en C comienza ejecutando la función llamada main, que en sistemas empotrados no suele tener argumentos de entrada o salida
Funciones (II) int suma(int a, int b); nombre Argumentos int suma(int a, int b); Este es el prototipo de la función, que anticipa al compilador qué argumentos admite Si el cuerpo de la función está antes de la llamada, el prototipo no es necesario void main(void){... int s=suma(10,3);... } /* S contendrá ahora un 13*/ Esta es la llamada a la función para que se ejecute, pasándole 2 parámetros compatibles con lo que establecen el prototipo y el cuerpo de la función int suma(int a, int b){ return a+b; } Este es el cuerpo de la función cuya cabecera coincide con el prototipo previo (salvo en el punto y coma final) Tipo del valor que devuelve
Funciones (III) Una variable, pasada como parámetro a una función, no ve modificado su valor original durante la ejecución (paso por valor, copia local) Para modificar, dentro de una función, el valor de una variable externa a la función, debemos pasarla por referencia (pasar su dirección) void suma(int a, int b, int *ps){*ps=a+b;} Por medio del puntero ps (que contiene la dirección de s), escribimos en s el resultado void main(void) {... int s; suma(10,3, &s );...} /* s contendrá ahora un 13*/ Ahora se pasa la dirección de s, para que dentro de la función se pueda acceder a s
Funciones (IV) Los argumentos de entrada son variables locales al cuerpo de la función (aunque estén fuera de las llaves) Es posible definir otras variables locales Las variables locales son temporales (se implementan como direcciones en la pila o como registros) El valor que se devuelve también suele ser a través de pila o de registro int suma(int a, int b){ int s=a+b; return s; } La variable local s sólo puede ser usada dentro de la función suma (ámbito). La función devuelve el contenido de s int * suma2(int a, int b) { int s=a+b; return &s; } ERROR: no se puede devolver la dirección de s, porque es local y desaparece cuando se termina de ejecutar la función
Bibliografía Brian W. Kernighan & Dennis M. Ritchie “The C Programming Language”, Second Edition. Prentice Hall, Inc., 1988. A. Clements “Microprocessor Systems Design” 3rd ed. PWS-Kent Pub. Co., 1997 H. Schildt “C: Manual de referencia” , Ed. McGraw Hill, 1989