240 likes | 404 Views
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”
E N D
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 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) • 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> • 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;
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 */ 0 2 i i $XXX $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; • 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) array 0 0 $XXX $XXX array 1 2 $XXX+4 $XXX+4 2 2 $XXX+8 $XXX+8 3 0 i $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’). string ‘S’ ‘T’ $XXX $XXX string ‘E’ ‘E’ $XXX+1 $XXX+1 ‘D’ ‘D’ $XXX+2 $XXX+2 0 0 $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) 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