peron:~$ cat compilar.txt

Tutorial de compilación

Este tutorial te ilustrará con ejemplos básicos cómo se traduce el código fuente de un programa en lenguaje C de alto nivel a un programa ejecutable en este sistema compartido y otros compatibles.

Este tutorial no es uno de programación, utilizaremos código pre-hecho.

Introducción

Si bien es posible crear programas utilizando lenguajes de bajo nivel como el ensamblador, normalmente querrás utilizar lenguajes de alto nivel.

La diferencia radica en que los lenguajes de alto nivel son portables, mientras que los de bajo nivel - además de ser más difíciles de aprender - están imbuidos de las particularidades del microprocesador en donde se ejecutan. Es por ello que aprender los lenguajes de alto nivel permite escribir programas para múltiples computadoras, que sean -además - más simples de compartir.

El lenguaje C diseñado por Dennis Ritchie (uno de los creadores de Unix) es uno de tales lenguajes, y se revela especialmente útil para la programación portable. Podrás utilizarlo en Texto-plano no sólo para emprender proyectos avanzados, sino también para aprender los pasos fundamentales para opera con lenguajes de alto nivel: su compilación.

Programación

La programación es un arte que involucra una serie de procesos mujer/máquina para resolver un problema determinado.

Consideremos los procesos de la programación:

Acción realizada: Genera: Opción de CC para detener en este paso:

Imaginar Percepción de un problema

Considerar un problema de programación código in mente (esto es Cerebral)

Edición del código fuente código fuente (para esto usarás un editor)

1. Preprocesado código fuente preprocesado -E

2. Compilación código ensamblador -S

3. Ensamblado código objeto -c

4. Enlazado binario ejecutable

Como puede verse, los últimos 4 pasos maquinales de la programación con lenguajes de alto nivel son practicados por medio de un Compilador, un programa de computadora capaz de traducir el código fuente escrito en un lenguaje en otro lenguaje distinto de operación (el código destino).

En nuestro sistema compartido utilizaremos el conjunto de compiladores CC (puedes también utilizar GCC, en GNU/Linux). Este super-compilador por línea de comandos es capaz de afrontar en traducir tu código o cualquiera que te hayan compartido.

Compilación básica

La manera más simple de llamar a CC es esta:

cc codigo_fuente.c –o binario_ejecutable

De esta manera, CC oficiará de compilador automático, y procesará un fichero de código fuente llamado codigo_fuente.c para generarar como resultado un fichero de destino ejecutable, llamado binario_ejecutable. Si todo va bien, CC será tan parco que no informará nada, simplemente compila y crea el ejecutable sin siquiera saludar.

Incluso la opción -o que indica el fichero ejecutable de destino es opcional: si no se indicas nada, CC almancenará el resultado de la compilación en el fichero ejecutable de nombre genérico llamado a.out.

Hola Tercer Mundo!

Si hay un programa obligatorio a la hora de empezar a programar en cualquier lenguaje de programación, ese es el mítico “Hola, Tercer Mundo!”.

La manía de utilizar un programa que dé salida en un terminal la cadena “Hola, Tercer Mundo!” para ejemplificar el funcionamiento del proceso de compilación en un determinado lenguaje se remonta –una vez más- a los orígenes del UNIX, el lenguaje C, con Kerningan, Ritchie, Thompson y compañía haciendo de las suyas.

Para compilar este saludo universal conviene organizar un directorio ~/src/holamundo/ para destinar el código fuente:

mkdir -p ~/src/holamundo ; cd ~/src/holamundo/

Ya en tu directorio ~/src/holamundo, crea un fichero llamado holamundo.c, e incorpórale el siguiente código fuente en lenguaje C:

// * * * Programa simple en C para mostrar "Hola Tercer Mundo!" * * *
#include <stdio.h>
 
int main()
{
    printf("Hola Tercer Mundo!");
 
    return 0;
}

Guarda los cambios y vuelve al Shell.

Compilación Automática

La manera más simple de resolver los cuatro pasos maquinales con el compilador automático CC (o GCC en GNU/Linux), es utilizar la siguiente sintaxis:

cc holamundo.c

Si todo marcha sobre ruedas, el compilador CC elaborará un fichero ejecutable a.out.

Córrelo con:

./a.out
Hola Tercer Mundo!

Si bien la compilación automática es sumamente simple y conveniente, ¡no nos muestra los pasos intermedios! Por demás, este código sólo da salida a una cadena de texto en el terminal.

¡Aprenderemos, en cambio, los cuatro pasos de la compilación con un juego!

Microtetris

Compilaremos ahora un clon del Tetris.

Crea un directorio para trabajar, con:

mkdir -p ~/src/microtetris/ ; cd ~/src/microtetris/

Ya en este directorio, crea el fichero microtetris.c. El código fuente en lenguaje C pesa 9,6K.

Ahora compila, pero siguiendo los 4 pasos maquinales de la compilación.:

1. Preprocesar

Preprocesa primero el código fuente microtetris.c, analizándolo con el párser de CC:

cc microtetris.c -E -o microtetris.p

Obtendrás así un fichero de código fuente preprocesado de microtetris.p (en este caso "se inflará" pensando unos 41K). Puedes curiosear el código preprocesado (o “parseado”) microtetris.p.

2. Compilar

Una vez parseado, podrás compilar tu código fuente con:

cc microtetris.c -S

La compilación traducirá el código fuente de alto nivel en un código fuente de bajo nivel específico para el procesador de la máquina, denominado código ensamblador (el fichero se llamara micreotetris.s).

Al examinar microtetris.s verás un conjunto de instrucciones ("nemónicos") que representan simbólicamente instrucciones básicas para el microprocesador. Dependiendo del tipo de arquitectura, este código puede resultar más inflado aún, en este caso de 48K.

Programar en ensamblador es posible, pero es 5 veces más complejo, y no suele ser portáble para equipos diferentes.

3. Ensamblar

Ensambla el código en lenguaje ensamblador:

cc microtetris.s -c -o microtetris.o

Obtendrás ahora el código objeto microtetris.o, que corresponda a la arquitectura del sistema. Este se ha reducido (en este caso, hasta unos 17K).

El código objeto es el lenguaje interpretable directamente por una computadora o microcontrolador digital.

4. Enlazar

Para crear el ejecutable de código máquina binario debes entrelazar este código objeto (incluyendo sus librerías, si las tuviese), y sumarle los encabezados. De todo ello se encarga el enlazador de CC:

cc microtetris.o -o microtetris.out

Lograrás así final el fichero ejecutable de salida, consistente en el binario de código máquina microtetris.out (ya de 15K, los que requerirá para cargarse en la memoria de texto-plano).

Si utilizas cat para revisar el código objeto microtetris.o o el código máquina microtetris.out, muy probablemente recibirás en tu terminal caracteres ininteligibles. No es basura, ¡sólo una máquina de esta arquitectura podrá ejecutarlo! (Que un humano comprenda el código máquina es un cliché típico de la Ciencia Ficción).

Ejecutar

Una vez compilado, evalúa correr el fichero del código máquina de salida ejecutándolo en tu sistema:

./microtetris.out

¡Felicitaciones! El programa corrió con éxito.

Limpiar y ordenar

Opcionalmente puedes limpiar tu ambiente de programación.

mv microtetris.out microtetris
rm ~/src/microtetris.p ~/src/microtetris.s ~/src/microtetris.o

Si lo deseas, también puedes eliminar el código fuente.

rm ~/src/microtetris.c

¡Ya has programado, compilado y probado!

Compilación Avanzada

La mayoría de los proyectos de software están formados por más de un fichero de código fuente, por lo que habrá que compilar varios de ellos de forma enlazada para generar un único binario ejecutable. Esto se puede hacer de forma sencilla llamando a CC con varios ficheros fuente y un ejecutable:

cc menu.c backend.c programa.c –o juego

Sin embargo es bastante probable que todos los ficheros fuente de un mismo proyecto no se encuentren en el mismo directorio, y que conforme el proyecto crezca, existan muchos ficheros de cabeceras (de extensión .h) y se alojen en directorios diferentes. Para evitar problemas a la hora de tratar con proyectos semejantes, podemos hacer uso de la opción de inlcusión -I, e incluir los ficheros que nos sean necesarios.

Imaginemos que tenemos un proyecto en el que todos los ficheros fuente están dentro del directorio ~/src/ y todos los ficheros de cabeceras están en el directorio include. Podríamos compilar el proyecto de la siguiente manera:

cc ./src/*.c –I include –o juego

Cuando un proyecto involucra muchos ficheros es bastante normal que no todas sus partes tengan las mismas opciones de compilación. Por ello es muy útil generar separadamente los respectivos códigos objeto, y cuando ya estén todos generados, enlazarlos para obtener el ejecutable:

cc –c backend.c –o backend.o

cc –c programa.c –lgraficos –o programa.o

cc –c menu.c –lcurses –o menu.o

cc kackend.o programa.o menu.o –o juego

Librerías

Conforme un proyecto va ganando entidad se hace casi irremediable el uso de librerías (realmente son “bibliotecas”) de funciones, que permiten reutilizar código de manera cómoda y eficiente. Para utilizar librerías estándar en el sistema es necesario emplear la opción -l a la hora de llamar a CC.

cc –c programa.c –lcurses –o programa.o

La compilación de este fichero (programa.c) requiere que esté instalada la librería curses o ncurses en el sistema, por ejemplo (la librería se llamará casi con seguridad libncurses). Si la librería no es una librería estándar en el sistema, sino que pertenece únicamente a nuestro proyecto, podremos indicar la ruta empleando la opción -L:

cc –c programa.c –L./libs/librería-programa –o programa.o

Optimizaciones

CC permite optimizar el código generado. Existen 3 etapas de optimización de código objeto:

Opciones de Optimización

Si queremos tener un control total acerca de las opciones de optimización empleadas por el compilador, podremos utilizar la opción -f:

Depuración

Es sumamente corriente cometer múltiples errores de programación, también conoicidos como “bugs”. Buscar estos errores comunes se denomina depurado o “debugging”.

La manera más simploide de depurar en C es llenar el código fuente de llamadas a printf() sacando resultados intermedios. En muchas ocasiones hacemos de ello un arte y utilizamos variables de preprocesado para indicar qué parte del código es de depurado y cuál no. Para indicar una variable de preprocesado en GCC se utiliza la opción -D:

cc –DDEBUG prueba.c –o prueba

El método recomendado es utilizar las opciones -g o -ggdb para generar información de depuración extra en nuestro ejecutable y poder seguir de forma más cómoda su ejecución mediante el GDB (“GNU Debugger”).

Si deseamos obtener todas las posibles advertencias en cuanto a generación del ejecutable partiendo de nuestro código fuente, emplearemos -Wall para solicitar todas las advertencias (“warnings”) en los que incurra nuestro código.

Así mismo, podríamos utilizar la opción -ansi o -pedantic para tratar de acercar nuestros programas al estándar ANSI C.


Hay que tener especial cuidado en añadir el directorio a la hora de llamar al ejecutable (./holamundo) porque en GNU/Linux la variable PATH no contiene al directorio actual. Así, por mucho que usemos cd para cambiar a un determinado directorio, siempre tendremos que incluir el directorio en la llamada al ejecutable, en este caso incluimos el directorio actual, es decir, ..


Arkurses

Sabiendo todo esto, prueba introducir y compilar el código fuente de Arkurses. Se trata de un clon del juego Arkanoid que utiliza la características de la librería de gráficas de terminal curses. Te presentará las dificultades explicadas anteriormente.

Podrás compilarlo directamente utilizando:

cc arkurses.c -lpanel -lncurses -o arkurses -Os -march=native

…o bien podrás realizar cada paso de la compilación, agregando las librerías ncurses y panel, introduciendo todo esto:

  • #preprocesa y muestra el código parseado
  • cc arkurses.c -E -o arkurses.p -march=native ; cat arkurses.p ;
  • #compila arkurses.c con sus librerías y muestra el ensamblador resultante:
  • cc arkurses.c -lpanel -lncurses -Os -S -march=native ; cat arkurses.s ;
  • #ensambla
  • cc arkurses.s -lpanel -lncurses -c -Os -march=native -o arkurses.o ;
  • #enlaza todo y crea el binario de salida
  • cc arkurses.o -lpanel -lncurses -o arkurses.out -Os -march=native ;
  • #Renombra el fichero de salida de compilación
  • mv arkurses.out arkurses
  • Una vez compilado, pruébalo con:

    ./arkurses

    (Puedes salir del juego con la tecla q).

    Luego elimina los sobrantes y deja el ejecutable, con

    rm arkurses.c arkurses.p arkurses.s arkurses.o

    Conclusión

    Preprocesar, base para compilar. Compilar, base para ensamblar. Ensamblar, base para enlazar. Y enlazar, base para Ejecutar.

    peron:~$

    Retorno