El entorno de desarrollo que vamos a emplear está formado por un conjunto de herramientas. Por un lado, tenemos la cadena de herramientas de desarrollo de GNU. Esta cadena de herramientas está compuesta por un conjunto de aplicaciones que se utilizan para la compilación, enlazado, análisis y depuración de programas software. Todas ellas son de libre distribución y gratuitas, y han sido desarrolladas como parte del proyecto GNU. Las herramientas que nosotros vamos a utilizar son las siguientes:
as
y ld
, que son el ensamblador y el enlazador, respectivamente, que vamos a emplear a la hora de generar nuestros archivos binarios ejecutables.Además de esta cadena de herramientas, vamos a utilizar un entorno integrado de desarrollo denominado Eclipse. Eclipse es una aplicación de escritorio que facilita el desarrollo de programas. Por un lado, Eclipse integra un editor de textos con el que poder editar el código fuente de los programas y, por otro, proporciona una interfaz homogénea que facilita y automatiza el uso de la cadena de herramientas de desarrollo GNU antes mencionadas.
La cadena de herramientas GNU se puede instalar desde el Terminal. El Terminal es un programa que proporciona una interfaz con el sistema operativo basada en comandos. Un comando es una cadena de caracteres que establece la operación que queremos que realice el sistema. Esta interfaz, mucho más áspera que las interfaces gráficas basadas en ventanas, permite un mayor control sobre el sistema y facilita la automatización de tareas. Además, es ampliamente utilizada para acceder a otros sistemas de forma remota, sobre todo por su baja demanda de ancho de banda.
Para abrir el Terminal, tenemos que lanzar la aplicación desde nuestro sistema de ventanas. La siguiente imagen muestra el entorno de ventanas de Ubuntu 20.04:
Para poder acceder al terminal, es necesario hacer click en “Activities” en la esquina superior izquierda de la pantalla o en el icono de nueve puntos que aparece en la esquina inferior izquierda. A continuación, aparecerá una caja de texto que nos permitirá buscar aplicaciones. Debemos buscar la aplicación “Terminal” y hacer click sobre ella:
Para introducir un comando, no tenemos más que teclear la cadena correspondiente en el terminal y, a continuación, pulsar la tecla Intro
para ejecutarlo. La ventana del intérprete es la que muestra a continuación:
Para instalar la cadena de herramientas de GNU, es necesario introducir el siguiente comando desde el terminal:
$
al comienzo del comando. Éste únicamente representa el comienzo de la línea.
sudo
se emplea para ejecutar un comando en modo superusuario o administrador. Cuando lo lancemos, pulsando la tecla Intro
, nos pedirá una contraseña que será la del propio usuario. Si el usuario tiene los privilegios suficientes, podrá lanzar el comando.
Para instalar Eclipse, es necesario abrir un navegador (e.g. Firefox) desde la máquina virtual y descargar el programa desde este enlace.
Eclipse es un programa que permite su propia extensión mediante módulos instalables denominados plug-ins. Desde la propia página de Eclipse, podemos descargar distintas “versiones” o “paquetes” de la herramienta. Cada una de estas versiones está orientada al desarrollo de un tipo determinado de aplicaciones, y llevará pre-instalados un conjunto específico de plug-ins. Nosotros vamos a utilizar la versión:
Eclipse IDE for Embedded C/C++ Developers
Para descargarla, es necesario seleccionar el archivo correspondiente al sistema operativo Linux y a la arquitectura que utilicemos, e.g. x86_64 para una arquitectura Intel de 64 bits. La descarga del archivo comprimido que contiene la aplicación se realizará de forma automática y, por defecto, se almacenará en el directorio Downloads
o Descargas
asociado a la cuenta de usuario Linux que estemos utilizando.
Para poder descomprimir el programa, es necesario ejecutar el siguiente comando desde el Terminal:
El comando descomprime el archivo y lo almacena en el directorio /opt
. Para poder ejecutar Eclipse, es necesario crear un enlace o acceso directo al archivo del programa dentro de uno de los directorios de aplicaciones del sistema. Para ello, tenemos que ejecutar el siguiente comando:
Una vez creado el acceso directo, podemos ejecutar Eclipse desde el Terminal con el siguiente comando:
Eclipse es un entorno de desarrollo que permite gestionar distintos proyectos. Un proyecto está formado por un conjunto de archivos fuente distribuidos en distintos directorios o carpetas, a partir de los cuales se puede obtener, tras un proceso de construcción (building), un archivos binario ejecutable. Eclipse almacena los proyectos activos dentro de un directorio específico denominado workspace. Este directorio puede estar localizado en cualquier lugar de nuestro sistema de archivos y llevar cualquier nombre.
Al arrancar Eclipse, el programa presenta un cuadro de diálogo en el que se solicita al usuario que introduzca la ruta al workspace que se va a utilizar durante la ejecución.
En nuestro caso, seleccionaremos la ruta de directorio por defecto, i.e., un directorio llamado eclipse-workspace
localizado en el directorio de conexión de nuestro usuario. Si no queremos tener que confirmar la ruta del workspace cada vez que entramos en la aplicación, podemos seleccionar la opción Use this as the default and do not ask again
antes de pulsar el botón Launch
.
La primera vez que arrancamos Eclipse, nos encontramos con una vista de bienvenida. Tras cerrar la vista de bienvenida, el programa nos presenta una perspectiva (perspective) inicial, esto es, un conjunto de vistas (views) redimensionables que nos muestran un aspecto determinado de nuestro entorno de desarrollo. La perspectiva principal que vamos a utilizar es la perspectiva C/C++
. Esta perspectiva contiene, por defecto, cuatro agrupaciones de vistas. En la agrupación izquierda se sitúa únicamente la vista del Explorador de Proyectos (Project Explorer), que nos servirá para examinar todos los archivos y directorios que forman nuestros proyectos. En la agrupación central está la Vista de Edición (Edit View), que nos permitirá modificar el contenido de los archivos fuente de nuestros proyectos. En la agrupación derecha tenemos varias vistas, seleccionables mediante sus pestañas correspondientes. Entre ellas, destaca la vista de Resumen (Outline). En esta vista aparecerán una lista con los elementos principales del archivo que en ese momento estemos editando, permitiéndonos mover entre ellos de forma rápida y sencilla. Por último, en la agrupación inferior tenemos un conjunto de vistas de soporte. En él se encuentra la vista de Problemas (Problems), en la que aparecerán los posibles errores que encontremos durante el proceso de compilación y generación de los programas.
Como toma de contacto con la herramienta, vamos a crear un primer proyecto. Para ello, seleccionamos la opcion File -> New -> C/C++ Project del menú principal. Al hacerlo, aparecerá un cuadro de diálogo en el que deberemos seleccionar la plantilla de proyecto que vamos a utilizar:
En este caso, seleccionaremos la plantilla C Managed Build
. Esta plantilla se utiliza para crear proyectos que tienen como base el lenguaje de programación C y en los que Eclipse se encarga de la gestión de los archivos fuente del proyecto, incluyéndolos de forma automática en el proceso de compilación. Tras pulsar el botón Next
, en la siguiente página del cuadro de diálogo, deberemos de seleccionar el nombre del proyecto y el tipo y la cadena de herramientas (toolchain) que vamos a utilizar. Como nombre del proyecto, podemos elegir cualquiera, como, por ejemplo: hello-world
. El tipo de proyecto está ligado al producto final que vamos a obtener como resultado del proceso de construcción. En este caso, seleccionaremos uno de los tipos clasificados como Executable
, ya que vamos a generar un programa binario ejecutable. En concreto, vamos a seleccionar el tipo Empty Project
, con objeto de crear un proyecto inicialmente vacío.
Una vez seleccionado el tipo de proyecto, tenemos que seleccionar la cadena de herramientas que vamos a utilizar para generar el ejecutable. En este caso, disponemos de varias cadenas de herramientas que nos permiten generar ejecutables tanto para la plataforma sobre la que estamos trabajando, como para otras plataformas mediante el empleo de un compilador cruzado. Un compilador cruzado es un programa que traduce sentencias descritas en un lenguaje de alto nivel (e.g. en lenguaje C), a instrucciones binarias de una arquitectura y/o un sistema operativo distintos de aquellos en los que se ejecuta el propio compilador. En nuestro caso no vamos a utilizar un compilador cruzado, ya que vamos a desarrollar programas que van a ejecutarse sobre el propio sistema en el que estamos trabajando. Por ello, vamos a seleccionar Linux GCC
como la cadena de herramientas que vamos a utilizar para generar los programas ejecutables. Una vez seleccionados el tipo y la cadena de herramientas, podemos pulsar sobre el botón Finish
para dejar que Eclipse termine de generar el proyecto a partir de la plantilla.
Una vez creado el proyecto, éste aparecerá en la vista de exploración de proyectos. El proyecto tiene una estructura predefinida que podemos visualizar pulsando el icono . Inicialmente, puesto que el proyecto está vacío, únicamente aparecerá un desplegable denominado Includes
. Este desplegable contiene la lista de todos los archivos de cabecera correspondientes a las bibliotecas de código que podemos enlazar junto con nuestro programa. Dentro de estas bibliotecas de código ya implementado y compilado se encuentra la biblioteca estándar de C antes mencionada. Los archivos de cabecera contienen, entre otros, los prototipos de las funciones a las que podemos llamar desde nuestro código. Estos prototipos "declaran" el formato de las funciones, esto es, su nombre, el número y tipo de sus argumentos, y el tipo de su valor de retorno.
Para comenzar, vamos a crear un nuevo directorio para almacenar archivos de código fuente. Para ello, pulsamos con el botón derecho sobre el proyecto en la vista de exploración. Del menú desplegable que aparece a continuación, seleccionamos la opción New -> Source Folder.
A continuación, aparecerá un cuadro de diálogo en el que se nos solicitará que introzucamos el nombre del directorio. En nuestro caso, introduciremos como nombre src
y, por último, pulsaremos el botón Finish
.
El siguiente paso será crear un primer archivo de código fuente. Para hacerlo, tenemos que pulsar con el botón derecho del ratón sobre el directorio que acabamos de crear para, a continuación, seleccionar la opción New -> Source File del menú desplegable.
En el cuadro de diálogo que aparece a continuación, deberemos seleccionar el nombre del archivo fuente que vamos a crear. El nombre del archivo deberá tener, en cualquier caso, la extensión .c
, y puede ser, por ejemplo: main.c
. El archivo lo podemos crear a partir de una plantilla o podemos crearlo completamente en blanco. La plantilla, por defecto, incluye al comienzo del archivo una cabecera con comentarios que contiene la fecha de creación y el nombre del usuario que ha creado el archivo.
En el archivo recién creado, vamos a incluir el siguiente código:
Este sencillo programa imprime por la salida estándar el mensaje "Hello world!" y, tras hacerlo, finaliza su ejecución. La línea 1 utiliza la directiva #include
para incluir el archivo stdio.h
dentro de nuestro código antes del proceso de compilación. Este archivo contiene los prototipos de un amplio conjunto de funciones de la biblioteca estándar de C relativas a la entrada/salida. Al incluir el archivo dentro de nuestro código, el compilador tendrá acceso a los mencionados prototipos y podrá codificar adecuadamente las instrucciones necesarias para poder ejecutar las llamadas a las funciones. En nuestro caso, necesitamos incluir el archivo para poder realizar la llamada a la función printf()
.
Las líneas 3 a 9 contienen la definición de la función main()
. Esta es la función principal del programa. Todos los programas que vamos a desarrollar van a tener una única función con ese nombre. La ejecución del programa comenzará siempre por esta función y finalizará cuando ésta retorna. El propio estándar del lenguaje C establece que el valor de retorno de la función main()
siempre va a ser tipo entero con signo (int
). Por convención, el valor de retorno indica cómo ha terminado la ejecución del programa. Si el valor de retorno es 0, eso quiere decir que el programa ha finzalizado correctamente. Si el valor de retorno es distinto de 0, eso quiere decir que el programa ha finalizado de manera errónea. Como se puede observar, en este caso el valor de retorno fijado en la línea 7 es siempre 0, lo que indica que el programa, llegado a ese punto, ha terminado sin errores.
La línea 5 implementa una llamada a la función printf()
. Esta función pertenece a la biblioteca estándar de C, y nos permite generar mensajes de texto que serán volcados en la salida estándar que será, por defecto, la consola. La función recibe como argumento la cadena de caracteres que vamos a producir durante la ejecución. En este caso se trata de una cadena de caracteres constante que expresamos entre comillas dobles (""). Los últimos dos caracteres de la cadena (\n
) no se corresponden con ninguna letra del alfabeto, sino que se emplean para codificar un código de control que permite generar un salto de línea al final del mensaje.
Una vez terminado de modificar el archivo de código fuente, tenemos que salvaro antes de poder construir el ejecutable final. Para ello, hemos de pulsar el botón de salvado (save) de la barra de herramientas, identificado con el icono , o pulsando la combinación de teclas Ctrl+s
.
Una vez salvado el archivo, procederemos a compilarlo para obtener la imagen binaria final del programa. Para ello, tenemos que pulsar el botón de construcción de la barra de herramientas, identificado con el icono de un martillo, o mediante la combinación de teclas Ctrl+b
. Si todo ha funcionado correctamente, se ha debido de crear en el proyecto un nuevo directorio denominado Debug
que contiene los archivos de construcción generados de forma automática por Eclipse, los archivos intermedios utilizados por el proceso de compilación, y propio programa ejecutable binario que llevará el mismo nombre que el proyecto. Además, también aparecerá un nuevo elemento en el proyecto denominado Binaries
, a partir del cuál tendremos acceso también al mismo programa binario.
Finalmente, una vez generado el programa binario, podemos ejecutarlo pulsando sobre el icono que aparece a continuación del botón Run
() de la barra de herramientas. Al hacerlo aparecerá un menú desplegable en el que seleccionaremos la forma de ejecutarlo, que en este caso será Run As -> Local C/C++ Application.
Una vez lanzado a ejecución, en la vista de consola (Console), localizada en el grupo de vistas inferior, aparecerá la salida del programa. En este caso, si la ejecución ha concluido con éxito, podremos observar el mensaje que hemos producido desde nuestro programa:
En la parte superior de la vista, aparecen el nombre y el estado del programa. En este caso, <terminated>
indica que el programa ha finalizado. Además, aparece también el valor de terminación (0).
En este segundo apartado vamos a aprender a depurar un programa desde Eclipse. La depuración es una técnica fundamental en el desarrollo y la validación de sistemas software. Mediante el empleo de un depurador, podemos ejecutar los programas de forma controlada, facilitando la localización de errores en el código. Para depurar nuestras aplicaciones, vamos a emplear la interfaz que proporciona Eclipse para el depurador GDB.
Antes de comenzar, vamos a realizar una pequeña modificación sobre el código que habíamos programado en el caso anterior:
En este caso, el programa imprime por la salida estándar 10 mensajes en líneas separadas con la cadena de caracteres "Hello world!". En la línea 5 se realiza la declaración de una variable denominada n
de tipo entero con signo y con un valor inicial de 0. Todas las variables de nuestro programa se almacenan en una o varias celdas de memoria. La cantidad de celdas requeridas para cada variable dependerá de su tipo y, en su caso, de la arquitectura para la que vayamos a compilar el programa. En este caso, la variable n
la vamos a utilizar como un contador para implementar un bucle while (while loop). Este tipo de estructura se emplea para ejecutar, de forma continua, un bloque de código mientras se cumpla una determinada condición. En el lenguaje C, este bucle se codifica con mediante la siguiente expresión:
La condición es una expresión que puede ser evaluada como verdadera (true) o como falsa (false). Si la condición es verdadera, se ejecutará el código del boque entre llaves {}
. Tras la ejecución de todas las sentencias del bloque, se volverá a evaluar la condición. Si ésta sigue siendo verdadera, se volverá a ejecutar el mismo bloque de código. Esta operación se repetirá mientras que la condición sea verdadera. Si tras una determinada iteración, la condición se evalúa como falsa, el programa abandonará el bucle y continuará la ejecución con la siguiente sentencia de código.
En el ejemplo de código sobre el que vamos a trabajar, el bucle while tiene como condición que la variable n
tenga un valor inferior a 10. La primera vez que se alcanza el bucle, la condición se evalúa como verdadera. Por tanto, el programa comenzará a ejecutar el bloque de código entre llaves y que abarca las líneas 7 a 13. Dicho código contiene, en la línea 9, una llamada a la función printf()
, que se encarga de producir el mensaje por la salida estándar. A continuación, en la línea 11 tenemos una sentencia de asignación. Las asignaciones se realizan siempre de derecha a izquierda, de tal forma que se le asigna, a la variable que aparece en el lado izquierdo del símbolo igual (=
), el valor resultante de evaluar la expresión que aparece a la derecha de dicho símbolo. En este caso, la sentencia de la línea 11 está asignando un nuevo valor a la variable n
. En concreto, está asignando como nuevo valor el resultado de sumar 1 al valor anterior que tuviera la misma variable. De esta forma, el valor de la variable n
cambia (se incrementa en una unidad) en cada iteración del bucle. Este incremento hace que, partiendo de un valor inicial de 0, y tras 10 iteraciones del bucle, la variable n
alcance el valor 10 y que, por tanto, la condición del bucle while deje de ser verdadera tras la ejecución de la última iteración. Así, el mensaje "Hello World!" se enviará un total de 10 veces por la salida estándar antes de concluir la ejecución del programa.
Una vez modificado y guardado el código, y tras construir el ejecutable, vamos a pasar a depurarlo. Para ello, tenemos que pulsar sobre el icono que aparece a continuación del botón Debug
() de la barra de herramientas. Al hacerlo aparecerá un menú desplegable en el que seleccionaremos la forma de depurar el programa, que en este caso será Debug As -> Local C/C++ Application.
A continuación, Eclipse muestra un cuadro de diálogo que nos sugiere cambiar de perspectiva, esto es, dejar la perspectiva C/C++
que veníamos utilizando, para pasar a la perspectiva de depuración o Debug
. En nuestro caso, vamos a aceptar el cambio pulsando sobre el botón Switch
. Si queremos que no nos vuelva a preguntar, podemos seleccionar la opción Remember my decision
.
La perspectiva de depuración contiene, al igual que la perspectiva C/C++
, cuatro agrupaciones de vistas. En la agrupación izquierda se sitúa únicamente la vista de Depuración (Debug), que nos servirá para realizar el seguimiento general del estado de la depuración de nuestro programa. En la agrupación central está la Vista de Edición (Edit View), idéntica a la desplegada por la perspectiva C/C++
. En la agrupación derecha tenemos varias vistas, entre la que destaca la vista de inspección de Variables. En esta vista aparecerá una lista con las variables que se encuentren en ese momento definidas dentro de nuestro programa. Por cada variable se muestra, además del nombre, el tipo y el valor actual. Por último, en la agrupación inferior tenemos un conjunto de vistas, incluyendo la vista de Consola (Console), idéntica a la mencionada en el apartado de ejecución, y la vista de Consola de Depuración (Debugging console), en la que podemos observar el estado de la ejecución del depurador GDB, utilizado internamente por Eclipse para realizar la depuración.
C/C++
, hay que pulsar el botón de cambio de perspectiva que se encuentra en la parte derecha de la barra de herramientas:
Tras comenzar la depuración, la ejecución del programa por defecto se detiene siempre al comienzo de la función main()
. La barra de herramientas de la perspectiva de depuración proporciona un conjunto de botones que nos permiten controlar la ejecución de nuestro programa.
De entre los botones de la barra de herramientas, destacamos los siguientes:
Resume
: continúa la ejecución del programa hasta que éste termina o alcanza un punto de ruptura (breakpoint). Los breakpoints se describen más adelante.Terminate
: finaliza la sesión de depuración, cerrando el depurador.Step into
: ejecuta la siguiente línea de código. Si la línea de código contiene una llamada a una función, el depurador entra dentro de la función y continúa la depuración línea a línea a partir de la primera línea de su código.Step over
: ejecuta la siguiente línea de código. Si la línea de código contiene una llamada a la función, ésta es ejecutada por completo y la depuración continúa a partir de la siguiente línea.A continuación, vamos a ejecutar el código completo paso a paso empleando el botón Step over
. A medida que vamos avanzando, en la vista Variables podemos observar el estado de la variable n
y cómo éste cambia con cada asignación. En la vista Consola se mostrarán los distintos mensajes a medida que vayan apareciendo.
Para facililtar la depuración y el trazado de los programas, los depuradores permiten fijar los llamados puntos de ruptura (breakpoints). Un punto de ruptura es un punto de interrupción señalado en el código de un programa que provoca que, cuando éste punto es alcanzado por un programa en depuración, se detenga su ejecución. Los puntos de ruptura se pueden colocar en cualquier línea de código que ejecute una expresión. Para fijar un punto de ruptura en una línea determinada, podemos hacer click con el ratón justo a la izquierda del número de la línea que muestra la Vista de Edición. Por ejemplo:
Una vez fijado, podemos lanzar la ejecución del programa depurado pulsando el botón Resume
. En este caso, la ejecución continuará de forma autónoma y se detendrá únicamente si el programa finaliza o si alcanza una línea en la que se haya fijado un punto de ruptura. Así, podemos centrar la depuración en un punto concreto de código. Si queremos que el programa continúe hasta finalizar o encontrar el siguiente punto de ruputura, sólo tenemos que volver a pulsar el botón Resume
.