C es un lenguaje de nivel medio y necesita un compilador que lo convierta en un código ejecutable para que el programa se pueda ejecutar en nuestra máquina.
¿Cómo compilamos y ejecutamos un programa en C?
A continuación se muestran los pasos que utilizamos en una máquina Ubuntu con el compilador gcc.
- Primero creamos un programa en C utilizando un editor y guardamos el archivo como nombredearchivo.c
$ vi filename.c
- El diagrama de la derecha muestra un programa sencillo para sumar dos números.
- Entonces compílelo usando el siguiente comando.
$ gcc –Wall filename.c –o filename
- La opción -Wall activa todos los mensajes de advertencia del compilador. Esta opción se recomienda para generar un mejor código.
La opción -o se utiliza para especificar el nombre del archivo de salida. Si no usamos esta opción, entonces se genera un archivo de salida con nombre a.out.
- Después de la compilación se genera el ejecutable y ejecutamos el ejecutable generado usando el siguiente comando.
$ ./filename
¿Qué ocurre dentro del proceso de compilación?
El compilador convierte un programa C en un ejecutable. Hay cuatro fases para que un programa en C se convierta en un ejecutable:
- Preprocesamiento
- Compilación
- Ensamblaje
- Enlace
Ejecutando el siguiente comando, obtenemos todos los archivos intermedios en el directorio actual junto con el ejecutable.
$gcc –Wall –save-temps filename.c –o filename
La siguiente captura de pantalla muestra todos los archivos intermedios generados.
Veamos uno a uno lo que contienen estos ficheros intermedios.
Preprocesamiento
Es la primera fase por la que pasa el código fuente. Esta fase incluye:
- Eliminación de comentarios
- Ampliación de macros
- Ampliación de los ficheros incluidos.
- Compilación condicional
- printf contiene ahora a + b en lugar de add(a, b) eso es porque las macros se han expandido.
- Los comentarios son despojados.
- #include<stdio.h> falta en su lugar vemos mucho código. Así que los archivos de cabecera se ha ampliado e incluido en nuestro archivo de origen.
El resultado del preprocesado se almacena en el nombre del fichero.i. Veamos qué hay dentro de filename.i: usando $vi filename.i
En la salida anterior, el archivo fuente se llena de mucha, mucha información, pero al final se conserva nuestro código.
Análisis:
Compilación
El siguiente paso es compilar filename.i y producir un; archivo de salida intermedio compilado filename.s. Este archivo está en instrucciones de nivel de ensamblador. Veamos a través de este archivo usando $vi filename.s
La instantánea muestra que está en lenguaje ensamblador, que ensamblador puede entender.
Ensamblaje
En esta fase se toma el archivo filename.s como entrada y se convierte en filename.o por el ensamblador. Este archivo contiene instrucciones a nivel de máquina. En esta fase, sólo el código existente se convierte en lenguaje de máquina, las llamadas a funciones como printf() no se resuelven. Veamos este archivo usando $vi nombredearchivo.o
Enlazar
Esta es la fase final en la que se realiza todo el enlace de las llamadas a funciones con sus definiciones. El enlazador sabe dónde están implementadas todas estas funciones. El enlazador también hace algún trabajo extra, añade algún código extra a nuestro programa que es necesario cuando el programa empieza y termina. Por ejemplo, hay un código que se requiere para configurar el entorno como pasar argumentos de línea de comandos. Esta tarea puede ser fácilmente verificada usando $size filename.o y $size filename. A través de estos comandos, sabemos que cómo el archivo de salida aumenta de un archivo objeto a un archivo ejecutable. Esto es debido al código extra que el linker añade con nuestro programa.