Programación (PRG) PRACTICA 3. Compilación: concepto. Ejecución de programas. Facultad de Informática Departamento de Sistemas Informáticos y Computación Universidad Politécnica de Valencia Curso 2002/2003 1. Introducción El objetivo de esta práctica es que el alum cozca el ciclo de vida de los programas más simples, es decir, el proceso que se sigue en la creación de programas. Dicho proceso sigue el esquema de edición, compilación y ejecución. La segunda parte de la práctica explica cómo ejecutar programas en Linux. 2. Compilación: concepto La Unidad Central de Proceso (CPU, del inglés Central Processing Unit), dirige y controla el procesado de información que realiza el computador. Cada tipo de CPU (Intel, Motorola, etc) tiene un lenguaje propio, llamado código máquina, y es el único que entiende. Este lenguaje consta de un conjunto reducido de instrucciones simples, entre las cuales hay: Operaciones aritméticas y lógicas (suma, resta, comparación, etc.). Lectura y escritura de datos en la memoria central. Instrucciones de control del flujo de programa. Instrucciones de entrada/salida de/a dispositivos periféricos. Un programa es una secuencia de instrucciones interpretable por alguna CPU y los lenguajes de programación sirven para especificar las secuencias de instrucciones que debe interpretar la CPU. El lenguaje máquina es el lenguaje que entiende directamente el microprocesador, pero escribir programas en lenguaje máquina es difícil, lento y poco fiable. Además, los programas 1
resultantes solo serán ejecutables en el mismo modelo de microprocesador (o en otro modelo que sea compatible). Los lenguajes de bajo nivel son lenguajes que están muy próximos al lenguaje máquina. El lenguaje ensamblador es una reformulación del lenguaje máquina que utiliza una tación abreviada (mnemotécnica) para que el programador pueda recordar sin dificultad las instrucciones de la CPU, direcciones de memoria, etc. En la Figura 1 se puede ver un fragmento de un programa en un procesador Intel. En la primera columna se muestran las direcciones de memoria, en la segunda el programa en, y en la tercera el código ensamblador. 0044E0D6 5F 0044E0D7 5E 0044E0D8 5B 0044E0D9 8BE5 0044E0DB 5D 0044E0DC C3 pop edi pop esi pop ebx mov esp,ebp pop ebp ret Figura 1: Porción de un programa en memoria Los lenguajes de programación de alto nivel están pensados para que las personas puedan escribir y entender programas de una manera mucho más sencilla que en lenguaje máquina o en ensamblador. Los lenguajes de alto nivel dependen de la máquina concreta donde se van a ejecutar. Son lenguajes portables. Para el desarrollo de las prácticas se va a utilizar el lenguaje de alto nivel C. Un programa es una versión particular de un algoritmo descrito en un lenguaje de programación determinado. Al proceso de escribir un programa que ejecute un algoritmo determinado se le demina codificación. A menudo se dice que el programa es una implementación del algoritmo. La compilación es el proceso de traducción de programas de alto nivel a código máquina. La entrada de este proceso de traducción se demina programa fuente y el resultado se demina programa objeto. El ciclo de creación de un programa suele pasar por las fases mostradas en la Figura 2. A continuación se muestra un programa en lenguaje C que muestra la cadena de texto Hola mundo. en pantalla: #include <stdio.h> int main() { printf("hola mundo.\n"); 14 de octubre de 2002 Página 2 de 8
PSfrag replacements Editor Compilador Errores de compilación? Beta-test Comportamiento correcto? Programa correcto Figura 2: Ciclo de vida de los programas más simples No hay procesador que entienda el C. Aunque escribas tu programa en C, lo que realmente ejecuta el procesador, y lo único que entiende es el código máquina (ver Figura 1). Ese código máquina lo ha producido un compilador (que es otro programa), a partir del código C. El compilador C por excelencia en el sistema operativo Linux es el compilador de GNU gcc. Al igual que el propio sistema operativo, el compilador gcc es gratuito, y se puede utilizar libremente. Los programas fuente en C se almacenan en ficheros con la extensión.c. Para compilar un programa que se encuentre en un fichero de texto llamado hola.c hay varias posibilidades. La más simple es: gcc hola.c Si el programa se ha escrito correctamente y hay errores sintácticos, el compilador generará un archivo llamado a.out, que es el programa ejecutable. Para dar otro mbre distinto al fichero ejecutable, se puede utilizar la opción -o, en cualquiera de las formas siguientes: gcc -o hola hola.c gcc hola.c -o hola La opción -o <mbre> le dice al compilador que genere un fichero ejecutable con el mbre dado. Un programa C puede estar compuesto por más de un fichero.c. Dado un programa compuesto por los ficheros: funcs.c, userio.c y main.c, se podría compilar con la orden: gcc -o programa funcs.c userio.c main.c Otras opciones interesantes del compilador son: 14 de octubre de 2002 Página 3 de 8
-Wall : muestra todos los avisos ( warnings ) generados en la compilación. Los avisos son mensajes que informan de aquellas construcciones que son inherentemente erróneas, pero que son peligrosas, o sugieren que podría haber un error. Aunque haya avisos, el compilador genera el ejecutable. -lm : enlaza la biblioteca ( library ) matemática. Es necesario incluir esta opción en aquellos programas que utilicen funciones matemáticas (por ejemplo, sqrt, sin, etc). 3. Ejecución de programas Una vez que se ha compilado el programa y se ha obtenido el fichero ejecutable, éste se puede ejecutar desde la línea de comandos del intérprete como si fuera un comando existente en el sistema. Cuando el usuario introduce una cadena de texto por teclado y pulsa INTRO, el intérprete de comandos asume que es una orden y procede a su ejecución. Si puede identificar la orden introducida por el usuario como un comando inter (por ejemplo, cd), asume que es un ejecutable exter. Para ejecutarlo, busca la variable de entor llamada PATH, que es la que determina los directorios que contienen ejecutables. El contenido de la variable PATH se puede examinar ejecutando en una consola de texto: [carraca@pc0401 carraca]$ echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/x11r6/bin Por lo tanto, si se intenta ejecutar el comando hola, el intérprete de comandos buscará un fichero ejecutable llamado hola en el directorio /usr/local/bin. Si lo encuentra, entonces buscará en el directorio /bin, y a sucesivamente. Si recorre todos los directorios y encuentra dicho archivo, mostrará un mensaje como el siguiente: [carraca@pc0401 carraca]$ hola bash: hola: command t found Si el fichero que se desea ejecutar está en un directorio de la variable de entor PATH, se le debe indicar al intérprete de comandos su ruta. Como se comentó en la práctica 1, para indicar la ruta de un fichero se puede hacer de forma relativa al directorio actual o de forma absoluta, respecto al directorio raíz. Por ejemplo, si se desea ejecutar el fichero hola que se encuentra en el directorio actual, habrá que introducir: [carraca@pc0401 carraca]$./hola 14 de octubre de 2002 Página 4 de 8
Recuérdese que el directorio. hace referencia al directorio actual. De la misma forma, si se desea ejecutar el programa hola, que se encuentra en el directorio padre del directorio actual, habrá que escribir: [carraca@pc0401 carraca]$../hola o, si se encuentra en la ruta /practicas2/alumnes/carraca: [carraca@pc0401 carraca]$ /practicas2/alumnes/carraca/hola PSfrag replacements Yo propongo, Linux dispone. Si consigues ejecutar tu programa, sigue los siguientes pasos: No ejecuta Incluye la ruta al ejecutar Existe el fichero? Estas ejecutando el.c? Esta fichero en el PATH? Compilalo chmod u+x <fichero> Tienes permisos? Llama al profesor 4. Ejercicios propuestos 1. Copia en un fichero llamado hola.c el código fuente del programa Hola mundo que se ha visto en la memoria y compílalo para generar el fichero ejecutable hola. 2. A continuación se muestra un programa que calcula el valor en pesetas de un número de euros que el usuario deberá introducir por teclado. Cópialo en un fichero y compílalo. #include <stdio.h> int main() { float euros; 14 de octubre de 2002 Página 5 de 8
int ptas ; printf("introduce el valor en euros y pulsa <INTRO>: "); scanf("%f",&euros); ptas=(int)(euros*166.386+0.5); printf("\n%f euros son %d pesetas.\n",euros,ptas); return 0; 3. El programa que se muestra a continuación sirve para comunicar dos ordenadores mediante un mini-chat. Una vez que lo hayas escrito y compilado (dando, por ejemplo el mbre minichat al ejecutable), un ordenador deberá ejecutar el programa sin parámetros. Una vez ejecutado, se podrá leer en pantalla: [carraca@pc0101 chat]$ minichat Mi dirección es: 158.42.179.3 El otro ordenador deberá ejecutar el programa indicando la dirección donde se debe conectar, por ejemplo: [tartana@pc0102 chat]$ minichat 158.42.179.3 Entonces se producirá la conexión, y ambos ordenadores se podrán cruzar mensajes, según los vaya pidiendo el sistema. El código fuente del programa, que deberás escribir, compilar y ejecutar es el siguiente: /* */ Adaptado de P.P. Ferreira y A. Pires. Publicado en Linux Gazette, Noviembre 1999 http://www.linuxgazette.com/issue47/bue.html #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <netdb.h> #include <string.h> main(int argc, char *argv[]) { int create_socket,new_socket,addrlen; char buffer[1024]; struct sockaddr_in address; 14 de octubre de 2002 Página 6 de 8
struct hostent *ht; printf("\x1b[2j"); if ((create_socket = socket(af_inet,sock_stream,0)) <= 0) { printf("error al crear el socket\n"); return -1; address.sin_family = AF_INET; address.sin_port = htons(15000); if (argc<=1) { /* Soy el servidor */ gethostname(buffer, 1024); ht = gethostbyname(buffer); if (ht == NULL) { printf("error se puede obtener la dirección local"); return(-1); printf("mi dirección es: %s\n",inet_ntoa(*((struct in_addr *)ht->h_addr))); address.sin_addr.s_addr = INADDR_ANY; if (bind(create_socket,(struct sockaddr *)&address,sizeof(address))!= 0) return -2; listen(create_socket,3); addrlen = sizeof(struct sockaddr_in); new_socket = accept(create_socket,(struct sockaddr *)&address,&addrlen); if (new_socket > 0) printf("conectado!!\n"); else return -3; do { printf("mensaje a enviar: "); fgets(buffer,sizeof(buffer),stdin); send(new_socket,buffer,sizeof(buffer),0); recv(new_socket,buffer,sizeof(buffer),0); printf("mensaje recibido: %s\n",buffer); while(strncmp(buffer,"/q",2)); close(new_socket); else { /* Soy el cliente */ inet_pton(af_inet,argv[1],&address.sin_addr); if (connect(create_socket,(struct sockaddr *)&address,sizeof(address)) == 0) printf("conectado!!\n"); else return -3; do { recv(create_socket,buffer,sizeof(buffer),0); printf("mensaje recibido: %s\n",buffer); if (strncmp(buffer,"/q",2)){ printf("mensaje a enviar: "); fgets(buffer,sizeof(buffer),stdin); send(create_socket,buffer,sizeof(buffer),0); while (strncmp(buffer,"/q",2)); 14 de octubre de 2002 Página 7 de 8
close(create_socket); return 0; 14 de octubre de 2002 Página 8 de 8