La compilación de un programa C ++ implica tres pasos:
Preprocesamiento: el preprocesador toma un archivo de código fuente C ++ y se ocupa de las directivas #include
s, #define
sy otras preprocesadores. El resultado de este paso es un archivo C ++ "puro" sin directivas de preprocesador.
Compilación: el compilador toma la salida del preprocesador y produce un archivo objeto a partir de ella.
Vinculación: el vinculador toma los archivos de objetos producidos por el compilador y produce una biblioteca o un archivo ejecutable.
Preprocesamiento
El preprocesador maneja las directivas del preprocesador , como #include
y #define
. Es independiente de la sintaxis de C ++, por lo que debe usarse con cuidado.
Funciona en un archivo C ++ fuente a la vez mediante la sustitución de #include
directivas con el contenido de los archivos respectivos (que suele ser sólo declaraciones), haciendo la sustitución de macros ( #define
), y la selección de diferentes partes del texto en función de #if
, #ifdef
y #ifndef
directivas.
El preprocesador funciona en una secuencia de tokens de preprocesamiento. La sustitución de macros se define como el reemplazo de tokens por otros tokens (el operador ##
permite fusionar dos tokens cuando tiene sentido).
Después de todo esto, el preprocesador produce una única salida que es una secuencia de tokens que resultan de las transformaciones descritas anteriormente. También agrega algunos marcadores especiales que le dicen al compilador de dónde provino cada línea para que pueda usarlos para producir mensajes de error sensibles.
Se pueden producir algunos errores en esta etapa con un uso inteligente de las directivas #if
y #error
.
Compilacion
El paso de compilación se realiza en cada salida del preprocesador. El compilador analiza el código fuente puro de C ++ (ahora sin directivas de preprocesador) y lo convierte en código ensamblador. Luego invoca el back-end subyacente (ensamblador en la cadena de herramientas) que ensambla ese código en código de máquina produciendo un archivo binario real en algún formato (ELF, COFF, a.out, ...). Este archivo de objeto contiene el código compilado (en forma binaria) de los símbolos definidos en la entrada. Los símbolos en los archivos de objetos se denominan por su nombre.
Los archivos de objetos pueden referirse a símbolos que no están definidos. Este es el caso cuando utiliza una declaración y no proporciona una definición para ella. Al compilador no le importa esto, y felizmente producirá el archivo objeto siempre que el código fuente esté bien formado.
Los compiladores generalmente le permiten detener la compilación en este punto. Esto es muy útil porque con él puede compilar cada archivo de código fuente por separado. La ventaja que esto proporciona es que no necesita volver a compilar todo si solo cambia un solo archivo.
Los archivos de objetos producidos pueden colocarse en archivos especiales llamados bibliotecas estáticas, para facilitar su reutilización más adelante.
Es en esta etapa que se informan errores de compilador "regulares", como errores de sintaxis o errores de resolución de sobrecarga fallidos.
Enlace
El enlazador es lo que produce la salida de compilación final de los archivos de objetos que produjo el compilador. Este resultado puede ser una biblioteca compartida (o dinámica) (y aunque el nombre es similar, no tienen mucho en común con las bibliotecas estáticas mencionadas anteriormente) o un ejecutable.
Vincula todos los archivos de objetos reemplazando las referencias a símbolos indefinidos con las direcciones correctas. Cada uno de estos símbolos se puede definir en otros archivos de objetos o en bibliotecas. Si se definen en bibliotecas distintas de la biblioteca estándar, debe informar al vinculador sobre ellas.
En esta etapa, los errores más comunes son definiciones faltantes o definiciones duplicadas. Lo primero significa que las definiciones no existen (es decir, no están escritas) o que los archivos de objetos o las bibliotecas donde residen no se entregaron al vinculador. Esto último es obvio: el mismo símbolo se definió en dos archivos de objetos o bibliotecas diferentes.
Este tema se trata en CProgramming.com:
https://www.cprogramming.com/compilingandlinking.html
Aquí está lo que escribió el autor:
fuente
En el frente estándar:
una unidad de traducción es la combinación de archivos fuente, encabezados incluidos y archivos fuente menos las líneas fuente omitidas por la directiva de preprocesador de inclusión condicional.
El estándar define 9 fases en la traducción. Los primeros cuatro corresponden al preprocesamiento, los siguientes tres son la compilación, el siguiente es la creación de instancias de plantillas (produciendo unidades de creación de instancias ) y el último es la vinculación.
En la práctica, la octava fase (la creación de instancias de plantillas) a menudo se realiza durante el proceso de compilación, pero algunos compiladores la retrasan a la fase de vinculación y otros la distribuyen en las dos.
fuente
Lo flaco es que una CPU carga datos de direcciones de memoria, almacena datos en direcciones de memoria y ejecuta instrucciones secuencialmente fuera de las direcciones de memoria, con algunos saltos condicionales en la secuencia de instrucciones procesadas. Cada una de estas tres categorías de instrucciones implica calcular una dirección a una celda de memoria para ser utilizada en la instrucción de la máquina. Debido a que las instrucciones de la máquina son de una longitud variable dependiendo de la instrucción particular involucrada, y debido a que encadenamos una longitud variable de ellas juntas a medida que construimos nuestro código de máquina, hay un proceso de dos pasos involucrados en el cálculo y la construcción de cualquier dirección.
Primero, diseñamos la asignación de memoria lo mejor que podemos antes de saber qué ocurre exactamente en cada celda. Descubrimos los bytes, o palabras, o lo que sea que forme las instrucciones y literales y cualquier dato. Simplemente comenzamos a asignar memoria y a construir los valores que crearán el programa a medida que avanzamos, y anotamos cualquier lugar que necesitemos para regresar y corregir una dirección. En ese lugar, colocamos un maniquí para rellenar la ubicación y poder seguir calculando el tamaño de la memoria. Por ejemplo, nuestro primer código de máquina podría tomar una celda. El siguiente código de máquina puede tomar 3 celdas, involucrando una celda de código de máquina y dos celdas de dirección. Ahora nuestro puntero de dirección es 4. Sabemos lo que pasa en la celda de la máquina, que es el código operativo, pero tenemos que esperar para calcular qué pasa en las celdas de dirección hasta que sepamos dónde se ubicarán esos datos, es decir
Si hubiera un solo archivo fuente, un compilador podría producir teóricamente un código de máquina totalmente ejecutable sin un vinculador. En un proceso de dos pasos, podría calcular todas las direcciones reales de todas las celdas de datos a las que hace referencia cualquier carga de máquina o instrucciones de almacenamiento. Y podría calcular todas las direcciones absolutas a las que hacen referencia las instrucciones de salto absoluto. Así es como funcionan los compiladores más simples, como el de Forth, sin enlazador.
Un enlazador es algo que permite compilar bloques de código por separado. Esto puede acelerar el proceso general de creación de código y permite cierta flexibilidad con la forma en que los bloques se utilizan más tarde, en otras palabras, se pueden reubicar en la memoria, por ejemplo, agregando 1000 a cada dirección para desplazar el bloque por 1000 celdas de direcciones.
Entonces, lo que genera el compilador es un código de máquina aproximado que aún no está completamente construido, pero se presenta para que sepamos el tamaño de todo, en otras palabras, para que podamos comenzar a calcular dónde se ubicarán todas las direcciones absolutas. el compilador también genera una lista de símbolos que son pares de nombre / dirección. Los símbolos relacionan un desplazamiento de memoria en el código de máquina en el módulo con un nombre. El desplazamiento es la distancia absoluta a la ubicación de memoria del símbolo en el módulo.
Ahí es donde llegamos al enlazador. El enlazador primero golpea todos estos bloques de código de máquina de extremo a extremo y anota dónde comienza cada uno. Luego, calcula las direcciones que se van a corregir sumando el desplazamiento relativo dentro de un módulo y la posición absoluta del módulo en el diseño más grande.
Obviamente, he simplificado demasiado esto para que pueda intentar comprenderlo, y deliberadamente no he usado la jerga de los archivos de objetos, tablas de símbolos, etc., lo cual para mí es parte de la confusión.
fuente
GCC compila un programa C / C ++ en ejecutable en 4 pasos.
Por ejemplo,
gcc -o hello hello.c
se lleva a cabo de la siguiente manera:1. Preprocesamiento
Preprocesamiento a través del preprocesador GNU C (
cpp.exe
), que incluye los encabezados (#include
) y expande las macros (#define
).El archivo intermedio resultante "hello.i" contiene el código fuente expandido.
2. Compilación
El compilador compila el código fuente preprocesado en código ensamblador para un procesador específico.
La opción -S especifica que se produzca código de ensamblaje, en lugar de código objeto. El archivo de ensamblaje resultante es "hello.s".
3. Asamblea
El ensamblador (
as.exe
) convierte el código de ensamblaje en código de máquina en el archivo de objeto "hello.o".4. Linker
Finalmente, el enlazador (
ld.exe
) vincula el código del objeto con el código de la biblioteca para producir un archivo ejecutable "hola".fuente
Mire la URL: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
El proceso completo de compilación de C ++ se introduce claramente en esta URL.
fuente