Cada compilación de un archivo .cpp es un paso independiente, que de resultar exitoso genera un archivo objeto .o. Estos son los que se unen para formar el ejecutable final. Cuando un archivo .cpp se modifica, sólo hay que recompilar ese archivo y volver a enlazar para obtener el nuevo ejecutable. Si el archivo modificado era un .h, habrá que recompilar todos los cpp que incluyan (#include) directa o indirectamente a ese .h. En general, el IDE (o make/cmake/scons/ants/algo de eso) se encarga de determinar qué hay que recompilar en cada cambio, comparando las fechas de los fuentes y los objetos, de forma que mientras cambiamos las cosas no tengamos que reconstruir de cero todo el proyecto.
Y llegamos finalmente a las bibliotecas. Empecemos por decir que una biblioteca es un conjunto de clases, macros, funciones, datos, algo de código que pensamos reutilizar. Un programador aplicado, cuando escribe alguna clase o función por ejemplo, intenta escribirla de forma que sea más o menos genérica y pueda reutilizarse en situaciones parecidas, para que la próxima vez que necesite resolver el mismo problema pueda buscar ese código y reusarlo. Si analizamos el proceso de compilación descripto antes, el exe surge de la suma de los archivos objeto. Por esto, si uno dispone del archivo objeto de la biblioteca, puede utilizare eso como entrada para el enlazador, y entonces no necesita el código fuente. Pero esto no es tan así. Supongamos que mi biblioteca tiene una función que busca el mayor dato de una lista. Supongamos que mi programa (llamado cliente) quiere utilizar la biblioteca para buscar la mejor nota de un curso. Para generar el ejecutable necesito las versiones compiladas (objetos) de la biblioteca y del programa cliente. Pero para que el compilador pueda generar el objeto del programa cliente, tiene que asegurarse de que el código fuente de dicho programa sea válido, y para eso necesita, por ejemplo, verificar que la cantidad y los tipos de los argumentos con los que el programa llama a la función de la biblioteca sean correctos. Por esto, el fuente del programa cliente debe incluir los archivos de cabecera de la biblioteca (los .h, los que dicen qué tiene la biblioteca, pero no cómo hace lo que hace). Entonces, para usar la biblioteca necesitamos las cabeceras (.h) y los objetos. Solo si no tenemos los objetos, necesitaremos los fuentes para generarlos.
Pero todavía falta algo más. Los archivos objeto de la biblioteca pueden funcionar de dos formas: el enlazado con el programa cliente puede realizarse una sola vez al compilar, de forma de obtener realmente un ejecutable con todo lo necesario para correr; o bien puede realizarlo el sistema operativo cada vez que queremos correr el programa. El primer caso se conoce como enlazado estático, mientras que el segundo como enlazado dinámico. Un ejecutable enlazado estáticamente tiene dentro todo lo necesario para ejecutarse; mientras que uno enlazado dinámicamente, en realidad lo que hace es ir a buscar las partes que le faltan a otros archivos cada vez que se lo intenta ejecutar. Esas partes son los famosos archivos .dll en Windows, o los .so en GNU/Linux. La ventaja del enlazado dinámico es que el ejecutable es más pequeño (obviamente porque le falta algo todavía), y que si muchos programas utilizan una misma biblioteca (código común), la versión compilada de esta biblioteca puede estar solo una vez en el sistema para que todos los ejecutables que la requieran la compartan (y por ejemplo, si actualizo la biblioteca, cambiando un archivo afecto a todos los ejecutables). Cuando las bibliotecas se utilizan de forma dinámica, en el proceso de enlazado de la compilación lo que se hace es colocar en el ejecutable el código y la información necesaria para que el programa busque (mediante la ayuda del sistema operativo) las partes que le faltan. En Windows, por ejemplo, los archivos .dll se buscan en la propia carpeta del ejecutable y donde indique la variable PATH, que suele incluir la carpeta windows/system32 por ejemplo. En GNU/Linux, lo determina la variable LD_LIBRARY_PATH primero y algunas prefijadas luego (como /lib, /usr/lib y /usr/local/lib, o sus variantes lib64).
De lo expuesto se desprende que a la hora de compilar un programa para utilizar una biblioteca hay que decirle muchas cosas al compilador. Hay que decirle dónde están los archivos .h, cuáles son, dónde están los objetos, cuáles son, en qué orden debe enlazarlos, si elegimos enlazar dinámica o estáticamente, cómo compilar las llamadas para que las piezas encajen, etc. En el próximo post de la serie voy a explicar cómo se dice todo esto en un proyecto de ZinjaI, con algunos ejemplos, y los posibles errores típicos asociados a cada paso.
Este post es continuación de Compilación y bibliotecas (parte 2): Intérpretes vs Compiladores y sigue en Compilación y bibliotecas (parte 4): utilizar bibliotecas desde Zinja
Muy bueno, gracias, por fin sé lo que es compilar.
ResponderEliminar