Procesamiento de archivos de encabezado de proceso de compilación en lenguaje C

Procesamiento de archivos de encabezado

 

El compilador, el compilador lee el programa fuente (flujo de caracteres), analiza el léxico y la gramática, convierte las instrucciones de lenguaje de alto nivel en código ensamblador funcionalmente equivalente y luego convierte el programa ensamblador en lenguaje de máquina y de acuerdo con el sistema operativo La ejecución del formato de archivo requiere la vinculación para generar un programa ejecutable.
Archivo de encabezado del programa fuente C -> procesamiento precompilado (cpp) -> compilador mismo -> programa optimizado -> ensamblador -> enlazador -> archivo ejecutable
1. Compilar preprocesamiento
leer fuente c Programa, que procesa pseudo-instrucciones (instrucciones que comienzan con #) y símbolos especiales. Las
pseudo-instrucciones incluyen principalmente los siguientes cuatro aspectos
(1) Instrucciones de definición de macros, como #define Name TokenString, #undef, etc. Para la pseudoinstrucción anterior, lo que debe hacer la compilación previa es reemplazar todos los Nombres en el programa con TokenString, pero el Nombre como una constante de cadena no se reemplaza. Para este último, se cancelará la definición de una determinada macro, por lo que la aparición de la cadena ya no será reemplazada.

(2) Instrucciones de compilación condicional, como #ifdef, #ifndef, #else, #elif, #endif, etc. La introducción de estas pseudoinstrucciones permite a los programadores decidir qué códigos procesará el compilador definiendo diferentes macros. El precompilador filtrará esos códigos innecesarios de acuerdo con los archivos relevantes

(3) El archivo de encabezado contiene instrucciones, como #include "FileName" o #include <FileName>, etc. Una gran cantidad de macros (la más común son las constantes de caracteres) generalmente se definen mediante la pseudoinstrucción #define en el archivo de encabezado, y también contienen declaraciones de varios símbolos externos. El propósito principal de usar archivos de encabezado es hacer que ciertas definiciones estén disponibles para múltiples programas fuente C diferentes. Porque en el programa fuente de C que necesita usar estas definiciones, solo necesita agregar una instrucción #include en lugar de repetir estas definiciones en este archivo. El precompilador agregará todas las definiciones del archivo de encabezado al archivo de salida que produce para que el compilador las procese.

El sistema puede proporcionar los archivos de encabezado incluidos en el programa fuente c, y estos archivos de encabezado generalmente se colocan en el directorio / usr / include. # Inclúyelos en el programa para usar corchetes angulares (<>). Además, los desarrolladores también pueden definir sus propios archivos de encabezado. Estos archivos generalmente se colocan en el mismo directorio que el programa fuente C. En este momento, se deben usar comillas dobles ("") en #include.

(4) Símbolos especiales, el precompilador puede reconocer algunos símbolos especiales. Por ejemplo, el identificador de LÍNEA que aparece en el programa fuente se interpretará como el número de línea actual (número decimal), y FILE se interpretará como el nombre del programa fuente C que se está compilando actualmente. El programa precompilado reemplazará estas cadenas que aparecen en el programa fuente con los valores apropiados.


Lo que logra el programa precompilado es básicamente el "reemplazo" del programa fuente. Después de esta sustitución, se genera un archivo de salida sin definiciones de macro, sin instrucciones de compilación condicionales y sin símbolos especiales. El significado de este archivo es el mismo que el del archivo fuente sin preprocesamiento, pero el contenido es diferente. En el siguiente paso, este archivo de salida se traducirá en instrucciones de máquina como salida del compilador.
2. Etapa de compilación

El archivo de salida obtenido por la precompilación solo tendrá constantes. Como la definición de números, cadenas, variables y palabras clave en lenguaje C, como main, if, else, for, while, {,}, +, -, *, /, etc. Lo que debe hacer el precompilador es pasar el análisis léxico y el análisis gramatical, después de confirmar que todas las instrucciones cumplen con las reglas gramaticales, se traducen a una representación de código intermedio equivalente o código ensamblador.
3. Etapa de
optimización El procesamiento de optimización es una tecnología relativamente difícil en el sistema de compilación. Los problemas que conlleva no solo están relacionados con la tecnología de compilación en sí, sino que también tienen una gran relación con el entorno hardware de la máquina. La parte de optimización es la optimización del código intermedio. Esta optimización no depende del equipo específico. La otra optimización es principalmente para la generación de código de destino. En la figura anterior, colocamos la etapa de optimización detrás del compilador, que es una representación más general.

Para la optimización anterior, el trabajo principal es eliminar expresiones comunes, optimización de bucle (subcontratación de código, debilitamiento de fuerza, cambio de condiciones de control de bucle, fusión de cantidades conocidas, etc.), propagación de copias y eliminación de asignaciones inútiles, etc.

Este último tipo de optimización está estrechamente relacionado con la estructura de hardware de la máquina, lo más importante es cómo aprovechar al máximo los valores de las variables almacenadas en los registros de hardware de la máquina para reducir el número de accesos a la memoria. Además, cómo ajustar las instrucciones de acuerdo con las características de las instrucciones de ejecución del hardware de la máquina (como canalización, RISC, CISC, VLIW, etc.) para acortar el código de destino y aumentar la eficiencia de ejecución también es un tema de investigación importante.

El ensamblador debe ensamblar el código de ensamblaje optimizado y convertirlo en las instrucciones de máquina correspondientes antes de que la máquina pueda ejecutarlo.
4. Proceso de

ensamblaje El proceso de ensamblaje en realidad se refiere al proceso de traducir códigos en lenguaje ensamblador en instrucciones de la máquina de destino. Para cada programa fuente en lenguaje C procesado por el sistema de traducción, finalmente se obtendrá el archivo de destino correspondiente a través de este proceso. Lo que se almacena en el archivo de destino es el código de lenguaje de máquina del equivalente de destino al programa de origen.

El archivo de destino se compone de segmentos. Por lo general, hay al menos dos secciones en un archivo de objeto:

Sección de código Esta sección contiene principalmente instrucciones de programa. Esta sección es generalmente legible y ejecutable, pero generalmente no se puede escribir. 

El segmento de datos almacena principalmente varias variables globales o datos estáticos que se utilizarán en el programa. Generalmente, los segmentos de datos son legibles, grabables y ejecutables. 

Hay tres tipos principales de archivos de objeto en el entorno UNIX:

(1) Los archivos reubicables contienen código y datos adecuados para vincular otros archivos de objeto para crear un archivo de objeto ejecutable o compartido.

(2) Archivo de objeto compartido Este archivo almacena código y datos adecuados para vincular en dos contextos. El primero es que el vinculador puede procesarlo con otros archivos reubicables y archivos de objetos compartidos para crear otro archivo de objeto; el segundo es que el vinculador dinámico lo usa con otro archivo ejecutable y otros archivos de objetos compartidos Combínelos para crear una imagen de proceso.

(3) Archivo ejecutable Contiene un archivo que puede ser ejecutado por un proceso creado por el sistema operativo.

Lo que genera el ensamblador es en realidad el primer tipo de archivo objeto. Para los dos últimos, se necesita algún otro procesamiento, que es el trabajo del enlazador.

5. Programa de enlace

El archivo objeto generado por el ensamblador no se puede ejecutar inmediatamente y puede haber muchos problemas sin resolver. Por ejemplo, una función en un archivo fuente puede hacer referencia a un símbolo definido en otro archivo fuente (como una variable o llamada de función, etc.); una función en un archivo de biblioteca puede ser llamada en un programa, y ​​así sucesivamente. Todos estos problemas solo pueden resolverse mediante el procesamiento del programa de enlace.

El trabajo principal del enlazador es conectar archivos de objetos relacionados entre sí, es decir, conectar los símbolos a los que se hace referencia en un archivo con la definición del símbolo en otro archivo, de modo que todos estos archivos de objetos se conviertan en una instalación del sistema operativo. En el todo unificado de ejecución.

De acuerdo con los diferentes métodos de vinculación de las mismas funciones de biblioteca especificadas por el desarrollador, el proceso de vinculación se puede dividir en dos tipos:

(1) Vinculación estática En este modo de vinculación, el código de la función se copiará de la biblioteca de vínculos estáticos donde se encuentra al final Programa ejecutable. De esta forma, estos códigos se cargarán en el espacio de direcciones virtuales del proceso cuando se ejecute el programa. La biblioteca de enlaces estáticos es en realidad una colección de archivos de objeto, cada uno de los cuales contiene uno o un grupo de códigos de función relacionados en la biblioteca.

(2) Enlace dinámico De esta manera, el código de la función se coloca en un archivo de objeto llamado biblioteca de enlace dinámico u objeto compartido. Lo que hace el enlazador en este momento es registrar el nombre del objeto compartido y una pequeña cantidad de otra información de registro en el programa ejecutable final. Cuando se ejecuta este archivo ejecutable, todo el contenido de la biblioteca de vínculos dinámicos se asignará al espacio de direcciones virtuales del proceso correspondiente en tiempo de ejecución. El programa de enlace dinámico encontrará el código de función correspondiente según la información registrada en el programa ejecutable.

Para llamadas a funciones en archivos ejecutables, se pueden utilizar métodos de enlace dinámico o de enlace estático, respectivamente. El uso de enlaces dinámicos puede hacer que el archivo ejecutable final sea más corto y ahorrar algo de memoria cuando el objeto compartido es utilizado por varios procesos, porque solo se necesita almacenar una copia del código del objeto compartido en la memoria. Pero no es que el enlace dinámico sea necesariamente mejor que el enlace estático. En algunos casos, los enlaces dinámicos pueden provocar daños en el rendimiento.



Compilación de Makefile

makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接-----

        但是不是所有的文件都需要重新编译,makefile能够纪录文件的信息,决定在链接的时候需要重新编译哪些文件!

        

        在unix系统下,makefile是与make命令配合使用的。
 
举个例子吧,我现在有main.c 、window.c 、model.c 、data.c 4个.c文件和window.h 、model.h 、data.h 3个.h文件。

                    main.c是主程序,里面有main()函数。其他的都是模块。

                    

                    如果要生成最终的可执行文件,要做以下步骤:

                    1、分别编译window.c 、model.c 、data.c 、main.c ,将会得到3个目标文件:window.o 、model.o 、data.o 、main.o

                    2、把这4个.o (在windows下就是.obj)文件链接起来,得到main.out(在windows下就是main.exe)。

                    

                    那么这些文件就要有逻辑关系,否则编译器不知道怎么编译。

                    

                    all:main.out

                    main.out:main.o window.o model.o data.o

                    gcc -o main.out main.o window.o model.o data.o

                    

                    #上面的意思是说:

                    #all:main.out

                    如果想要编译所有:make all,那么将会生成main.out可执行文件。

                    

                    #main.out:main.o window.o model.o data.o

                    而要生成这个main.out,需要依赖main.o,window.o,model.o,data.o 4个文件。

                    

                    #    gcc -o main.out main.o window.o model.o data.o

                    这句是调用编译器编译,vc用的是cl。变异的时候可以加上很多的参数、定义的宏、链接库路径等。

                    

                    当然,还没有完呢,这些main.out依赖的这些 .o 怎么来的?

                    

                    window.o:window.c window.h

                    gcc -c window.c

                    

                    model.o:model.c model.h

                    gcc -c model.c

                    

                    data.o:data.c data.h

                    gcc -c data.c

                    

                    上面的-c参数是指定编译器编译出一个.o文件就可以了,不要再寻找main()函数做链接工作。

                    

                    这些和到一起,就是一个makefile,当然这些功能还太少,可以加上很多别的项目。但宗旨就是:

                    让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变,编译器会自动的发现最终的生成文件已经过时,

                    而重新编译相应的模块。

                    

                    现在的VC++真是太好了,不用一个字一个字的去敲Makefile 了。

Supongo que te gusta

Origin blog.csdn.net/geggegeda/article/details/4205977
Recomendado
Clasificación