Después de haber transformado nuestro código fuente en C a código ensamblador, el siguiente paso en el proceso de compilación es generar el archivo de código objeto. Este archivo, con extensión .o, contiene instrucciones en lenguaje máquina que el procesador puede entender directamente. Para obtenerlo, utilizamos el compilador GCC con la opción -c, por ejemplo, gcc -c hello.c. Esto produce un archivo binario que no es legible como texto, ya que contiene datos en formato máquina.
Aunque el archivo .o contiene instrucciones que el procesador reconoce, no es un programa ejecutable por sí mismo. Le falta una estructura fundamental que los sistemas operativos requieren para ejecutar un programa. Por ejemplo, en sistemas Linux, los ejecutables en formato ELF incluyen información sobre el espacio de memoria necesario, el tamaño de la pila y del heap, entre otros detalles. En Windows, los archivos .exe son aún más complejos, ya que pueden contener iconos, cadenas y otros recursos embebidos.
Para convertir estos archivos objeto en un programa ejecutable, necesitamos la etapa de enlazado. El enlazador, que en sistemas Unix suele ser ld y forma parte de Binutils, toma todos los archivos .o que componen nuestro programa y los une, resolviendo referencias entre funciones y variables, y añadiendo la estructura necesaria para que el sistema operativo reconozca el archivo como un ejecutable válido.
Podemos realizar el enlazado usando GCC directamente, pasando los archivos objeto como argumentos. Por defecto, el ejecutable resultante se llama a.out en sistemas Unix o a.exe en Windows con MinGW, aunque podemos cambiar el nombre con la opción -o. Por ejemplo:
gcc -o hello hello.o
También es posible compilar y enlazar en un solo paso, pasando directamente los archivos fuente a GCC:
gcc -o hello hello.c
GCC se encargará de realizar todas las etapas necesarias para generar el ejecutable final.
Cuando trabajamos en proyectos con múltiples archivos fuente, como en un programa que consta de empleados.c, sueldos.c y main.c, podemos compilar todo junto con:
gcc -o empleados empleados.c sueldos.c main.c
En este proceso, GCC compila cada archivo fuente por separado a código objeto y luego enlaza todos los objetos para crear el ejecutable final. Sin embargo, compilar todo de nuevo cada vez que hacemos un cambio puede ser ineficiente, especialmente en proyectos grandes.
Por eso, es recomendable compilar primero cada archivo fuente a su correspondiente archivo objeto con -c:
gcc -c main.c
gcc -c sueldos.c
gcc -c empleados.c
De esta forma, si modificamos solo uno de los archivos, solo necesitamos recompilar ese archivo y luego enlazar todos los objetos para obtener el ejecutable. Esto ahorra tiempo de compilación.
Para automatizar este proceso y evitar tener que escribir manualmente todos los comandos, podemos usar herramientas como make. Make permite definir reglas que indican cómo compilar cada archivo y cómo enlazarlos, de modo que solo recompila lo que ha cambiado y enlaza automáticamente. Esto es especialmente útil en proyectos con muchos archivos y dependencias.
En resumen, el flujo completo desde el código fuente hasta el ejecutable implica preprocesado, compilación a ensamblador, ensamblado a código objeto y finalmente enlazado. Entender cada etapa y cómo manejar los archivos .o nos permite optimizar el proceso de compilación y trabajar de forma más eficiente en proyectos complejos. Además, aprovechar herramientas como make facilita enormemente la gestión de compilaciones en proyectos con múltiples archivos.