top today populares likes hashtags for followers follow c++ c compilation fortran object-files

c++ - today - ¿Por qué compilar primero en un archivo de objeto?



tags for likes 2018 (5)

Compilamos para objetos de archivos para poder vincularlos para formar archivos ejecutables más grandes. Esa no es la única manera de hacerlo.

También hay compiladores que no lo hacen de esa manera, sino que compilan en la memoria y ejecutan el resultado inmediatamente. Antes, cuando los estudiantes tenían que usar mainframes, esto era estándar. Turbo Pascal también lo hizo de esta manera.

En el último año comencé a programar en Fortran trabajando en una universidad de investigación. La mayor parte de mi experiencia previa es en lenguajes web como PHP o ASP antiguo, por lo que soy un novato en compilar declaraciones .

Tengo dos códigos diferentes que estoy modificando.

Uno tiene una declaración explícita que crea archivos .o desde módulos (por ejemplo, gfortran -c filea.f90) antes de crear el ejecutable.

Otro es crear el archivo ejecutable directamente (a veces crear archivos .mod, pero no archivos .o, por ejemplo, gfortran -o ejecutable filea.f90 fileb.f90 mainfile.f90).

  • ¿Hay alguna razón (aparte de, quizás, Makefiles) para que un método sea preferido sobre el otro?

Cuando tiene un proyecto con unos pocos 100 archivos de origen, no desea volver a compilar todos ellos cada vez que uno cambia. Al compilar cada archivo de origen en un archivo de objeto independiente y solo recompilar esos archivos fuente que se ven afectados por un cambio, usted pasa la cantidad mínima de tiempo desde el cambio del código fuente al nuevo ejecutable.

make es la herramienta común utilizada para rastrear tales dependencias y recrear su binario cuando algo cambia. Por lo general, configura de qué depende cada archivo de origen (estas dependencias normalmente pueden ser generadas por su compilador, en un formato adecuado para make ) y permite manejar los detalles de la creación de un binario actualizado.


El archivo .o es el archivo de objeto. Es una representación intermedia del programa final.

Específicamente, típicamente, el archivo .o ha compilado el código, pero lo que no tiene son las direcciones finales para todas las diferentes rutinas o datos.

Una de las cosas que un programa necesita antes de poder ejecutar es algo similar a una imagen de memoria.

Por ejemplo.

Si tiene su programa principal y se llama rutina A. (Esto es Faux Fortran, no lo he tocado en décadas, así que trabaje conmigo aquí).

PROGRAM MAIN INTEGER X,Y X = 10 Y = SQUARE(X) WRITE(*,*) Y END

Entonces tienes la función CUADRADO.

FUNCTION SQUARE(N) SQUARE = N * N END

Las unidades son compiladas individualmente. Se puede ver que cuando se compila MAIN, NO SABE dónde está "SQUARE", en qué dirección se encuentra. Necesita saber que, así, cuando llama a la instrucción de microprocesadores JUMP SUBROUTINE (JSR), la instrucción tiene que ir a algún lugar.

El archivo .o ya tiene la instrucción JSR, pero no tiene el valor real. Eso viene más adelante en la fase de enlace o carga (dependiendo de su aplicación).

Por lo tanto, el archivo .o de MAINS tiene todo el código para main y una lista de referencias que desea resolver (en particular, SQUARE). SQUARE es básicamente independiente, no tiene ninguna referencia, pero al mismo tiempo, aún no tenía una dirección en cuanto a dónde está en la memoria.

El enlazador eliminará todos los archivos .o y los combinará en un solo exe. En los viejos tiempos, el código compilado sería literalmente una imagen de memoria. El programa comenzaría en alguna dirección y simplemente se cargaría en RAM al por mayor, y luego se ejecutaría. Entonces, en el escenario, puede ver al enlazador tomando los dos archivos .o, concatenándolos juntos (para obtener la dirección real de CUADRÍCULOS), luego retrocederá y encontrará la referencia CUADRADA en PRINCIPAL, y completará la dirección.

Los enlazadores modernos no llegan tan lejos, y difieren gran parte de ese procesamiento final cuando el programa está realmente cargado. Pero el concepto es similar.

Al compilar en archivos .o, termina con unidades de lógica reutilizables que luego se combinan mediante los procesos de enlace y carga antes de la ejecución.

El otro aspecto agradable es que los archivos .o pueden provenir de diferentes idiomas. Siempre que los mecanismos de llamada sean compatibles (es decir, cómo se pasan los argumentos a las funciones y los procedimientos y desde ellas), una vez compilado en un .o, el idioma de origen se vuelve menos relevante. Puede vincular, combinar, código C con código FORTRAN, por ejemplo.

En PHP y todo, el proceso es diferente porque todo el código se carga en una sola imagen en tiempo de ejecución. Puede considerar los archivos .o de FORTRANs de manera similar a como usaría los mecanismos de inclusión de PHP para combinar los archivos en un todo grande y cohesivo.


La compilación de archivos de objetos primero se llama compilación separada. Hay muchas ventajas y algunos inconvenientes.

Ventajas:

  • fácil de transformar archivos de objetos (.o) a bibliotecas y vincularlos más tarde
  • Muchas personas pueden trabajar en diferentes archivos de origen al mismo tiempo
  • compilación más rápida (no se compilan los mismos archivos una y otra vez cuando la fuente no ha cambiado)
  • los archivos de objeto pueden crearse a partir de diferentes fuentes de idioma y enlazarse entre sí en algún momento posterior. Para hacer eso, los archivos de objetos solo tienen que usar el mismo formato y convenciones de llamada compatibles.
  • la compilación por separado permite la distribución de bibliotecas de todo el sistema (bibliotecas de SO, bibliotecas estándar de lenguaje o bibliotecas de terceros) ya sea estáticas o compartidas.

Inconvenientes:

  • Hay algunas optimizaciones (como la optimización de las funciones de distancia) que el compilador no puede realizar, y al vinculador no le importa; sin embargo, muchos compiladores ahora incluyen la opción de realizar "optimización de tiempo de enlace", que niega en gran medida este inconveniente. Pero esto sigue siendo un problema para bibliotecas de sistemas y bibliotecas de terceros, especialmente para bibliotecas compartidas (imposible de optimizar las partes de un componente que pueden cambiar en cada ejecución, sin embargo, otras técnicas como la compilación de JIT pueden mitigar esto).
  • en algunos lenguajes, el programador debe proporcionar algún tipo de encabezado para el uso de otros que se vincularán con este objeto. Por ejemplo, en C tiene que proporcionar archivos .h para ir con sus archivos de objetos. Pero es una buena práctica de todos modos.
  • en lenguajes con texto incluido como C o C ++, si cambia un prototipo de función, debe cambiarlo en dos lugares. Una vez en el archivo de cabecera, una vez en el archivo de implementación.

Otra razón, aparte del tiempo de compilación, es que el proceso de compilación es un proceso de varios pasos .

Los archivos objeto son solo un resultado intermedio de ese proceso. Eventualmente serán utilizados por el enlazador para producir el archivo ejecutable.