资讯详情

编译和连接

编译和连接

  • 1、预编译
  • 2、编译
    • 2.1词法分析
    • 2.2语法分析
    • 2.3语义分析
    • 2.4中间代码
    • 2.5生成和优化目标代码
  • 3、汇编
  • 4、静态链接
    • 4.1基本概念
    • 4.静态链接的基本过程和作用:
摘自程序员的自我修养 ————————————————————————————————————————————————————————————

当我们通常编译程序时,比如下面的代码,当我们在windows在系统下使用编译软件,如VS2022年调试时,看似简单,我们只需点击本地windows我们的程序将编译并运行相应的结果。或者我们在linux在系统下使用GCC编译程序只需使用简单的命令gcc -o main main.c可以得到main.c可执行文件main。这个看似简单的操作背后有一个非常复杂的过程。

#include <stdio.h> int main() { 
           printf("hello world\n");    return 0; } 

事实上,编译程序主要分为预处理、编译、汇编和链接四个步骤。

1、预编译

相当于以下命令: gcc -E main.c -o main.i ①所有有的#####都放在一边define删除并扩展所有宏定义 ②处理所有条件的预编译指令,如#if”、“#ifdef”、“#elif”、“#else”、“#endif” ③处理“#include预编译指令将包含的文件插入预编译指令的位置。(注:此过程是递归的,即包含的文件也可能包含其他文件。 ④删除所有注释 ⑤添加行号和文件名标识。(注:添加行号的目的是方便编译器产生调试行号信息,并在编译过程中出现编译错误或警告时显示行号) ⑥保留所有##pragma由于编译器需要使用它们,编译器指令。

2、编译

编译过程相当于以下命令: gcc -S main.i -o main.s

2.1词法分析

首先输入源代码程序扫描器,扫描器用于词法分析(其中一个是lex实现这种扫描的程序),使用类似的程序算法可以很容易地将源代码的字符序列分成一系列

词法分析产生的标记分为四类: 关键词:程序语言定义具有固定意义的标志符 标识符:表示各种名称,如:变量名、函数名 字面量:包括数字、字符串等 特殊符号:如加号、等号……

2.2语法分析

语法分析完成了对表达式语法层面的分析,而不理解程序语句的真正意义。例如,乘法操作两个指针显然毫无意义,但在语法层面上是合法的。

语法分析器对扫描器产生的标记进行语法分析(一种称为yacc语法分析的工具)可以确定计算符号的优先级和含义。例如,乘法的优先级高于加法和星号。在C语言的句子中,是乘法还是指针等。如果括号不匹配,操作符缺乏,编译器会在这个阶段报告错误。

2.3语义分析

语义分析可以分析程序语句是否正确,是否具有真正的意义。例如,上述语法分析中提到的两个指针的乘法没有一定的意义,因此子语义分析将在此阶段报告错误。

静态语义分析由语义分析器完成。

静态语义:编译器可以确定的语义。包括声明与类型的匹配、类型转换等。 动态语义:操作期间的语义相关问题。

2.4中间代码

例如,我们的程序语句中有这样一句话

#define x 2 #define y 3 int main { 
             int a=x y;     ///预编译将宏定义扩展到:int a=2 3; } 

那么2 语义分析后,该表达值可优化为5。因此,优化代码变成int a=5;

2.5生成和优化目标代码

中间代码通过代码生成器转换为目标代码,然后目标代码优化器优化目标代码,如选择正确的搜索方法、删除多余的指令等。

3、汇编

汇编过程相当于以下命令: gcc -c main.s -o main.o 汇编器是将汇编代码转换为机器可执行的计算机指令。每个汇编句对应一个机器指令。

4、静态链接

4.1基本概念

编译器将源代码文件编译成未连接的目标文件,然后通过我们的链接器链接这些目标文件形成可执行文件。 比较项目通常由许多模块组成。在确保每个模块能够独立编译的前提下,我们组装所有模块。组装木块的过程是链接。链接过程主要包括: ①分配地址和空间 ②符号决议:确保所有目标文件中的符号引用都有唯一的定义 ③重定位:由于程序员在最原始的程序操作过程中在纸带上打孔,穿孔为0,未穿孔为1。但是,如果程序修改,则需要重新执行上述穿孔操作,并重新计算目标地址,这需要时间和精力。所以有一个链接器来完成上述工作。连接器修改了其他符号地址的一些汇编指令。地址修改的过程称为定位。

h2> 4.2静态链接的基本过程和作用:

通过下面的例子来理解静态链接的过程:

//加法模块的文件add.c
#include <stdio.h>
int add(int x,int y)
{ 
        
    return x+y;
}
//主程序main.c
#include <stdio.h>
int main()
{ 
        
    int a=1,b=2;
    int c=add(a,b);
    cout<<c<<endl;
    return 0;
}

我们在执行main程序的时候,会调用另外一个模块的add()函数,调用的时候需要知到add()函数的地址,但是由于模块之间时独立编译的,编译main.c的时候并不知道add函数的地址,因此把add函数的地址搁置,到最后链接的时候由链接器去修正add函数的地址。 所以链接器的作用是:在写程序的时候无需了解其他模块的函数和全局变量地址,可以直接使用。链接器会根据引用的符号(比如上述例子中的add函数,根据我们引用的符号add)自动去相应的模块(比如add.c模块)寻找相应的地址(寻找add函数的地址),然后在之前的模块程序中(main.c)将所引用(add函数)的指令重新修正,把它们(main.c程序中的add函数)的目标地址改为整整的add函数地址。

标签: 连接器自动组装机器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台