Build C Projects(编译和链接)

头文件

头文件只是一些声明以及宏定义,大可以这样理解。

库文件

向其他程序提供服务的代码。库其实是一组目标文件的包,一些最常用的代码编译成目标文件后打包存放。
最常见的库就是运行时库(Runtime Library),是支持程序运行的基本函数的集合。
库一般分为两种:静态库(.a,.lib)动态库(.so,.dll )所谓静态、动态是指链接过程。

静态库:在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。
动态库:在程序运行时才被载入。

A library file is a collection of obiect files

GCC的工作过程

.c 经编译预处理 -> .i  
.i 经编译产生汇编文件 -> .s   
.s 经汇编产生目标文件 -> .o  
.o 经过链接 生成可执行文件 -> .exe

编译预处理

由预处理器来完成。

预处理 处理以下指令 ,删除所有的注释(包括头文件中的),处理条件编译(为真的语句块会被保留),展开头文件
添加行号和文件标识:每行的格式是"# 行号 文件名 标志"。
以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号
保留所有的#pragma编译器指令,因为后面编译器要使用他们。

其中的"行号""文件名"表示从它后一行开始的内容来源于
哪一个文件的哪一行,标志可以是1,2,3,4四个数字,每个数字的含义如下:

1:表示新文件的开始
2:表示从一个被包含的文件中返回
3:表示后面的内容来自系统头文件
4:表示后面的内容应当被当做一个隐式的‘extern C’块
#include<iostream>
using namespace std;
#define PI 3.14159
#define FORMAT "%f\n"
#define PI_2  2*PI
#define PRT printf("%f ",PI); \
            printf("%f\n",PI_2);
int main(){
        printf(FORMAT,PI_2);
        PRT;
        return 0;               // 结束
}

经编译预处理后产生的 .i文件大小会比 .cpp 文件 大很多,查看生成的 .i文件(尾部的部分代码)

  extern wostream wcerr;
  extern wostream wclog;
										// 头文件中的内容, 其中头文件的注释 也被删除

  static ios_base::Init __ioinit;
}
# 2 "test.cpp" 2

# 2 "test.cpp"
using namespace std;

// define 被移除了
 int main(){
  printf("%f\n",2*3.14159);
  printf("%f ",3.14159); printf("%f\n",2*3.14159);;			// 可以看到发生了替换
  return 0;						// 注释被删掉了
 }

编译 Compilation

把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。

词法分析:扫描器(Scanner)将源代码的字符序列分割成一系列的记号(Token)记号可以分为:关键字,标识符,字面量(数字,字符串等),特殊符号(加号,等号等等)。识别记号的同时,扫描器也完成了其他工作,如:将标识符存到符号表,字面量存到文字表等等。以备后面的步骤使用。

语法分析:由语法分析器(Grammar Parser)对记号进行语法分析,从而产生语法树(Syntax Tree),以表达式(Expression)为节点的树。

语义分析:由语义分析器(Semantic Analyzer)分析语句是否有意义,类型是否匹配,等等。如指针和指针相乘,语法是合法的,但语义是没有意义的。

中间语言生成:由源码级优化器(Source Code Optimizer)在源代码级别进行优化,生成中间代码(Intermediate Code)。它一般和目标机器以及运行时的环境无关,比如不包括变量地址,寄存器名字,数据尺寸等等。

目标代码生成与优化:由代码生成器(Code Generator)将中间代码转换成目标机器代码 (汇编代码),这个过程依赖目标机器,因为不同的机器有不同的字长,寄存器,等等。和 目标代码优化器(Target Code Optimizer)对目标代码(汇编代码)进行优化,如:选择合适的寻址方式,删除多余的指令等等。
在这里插入图片描述
最后生成 .s 文件

汇编 Assembly

由汇编器来完成,相对编译器的工作比较简单。每一个汇编语句几乎都对应一条机器指令。根据汇编指令和机器指令的对照表一一翻译就可以了。
把汇编码转为机器码(二进制文件)
生成 .o 文件

链接 Linking

一个程序被分割成多个模块后,这些模块最后如何组合形成一个单一的程序,可以归结为模块之间如何进行通信。
最常见的 C/C++ 模块之间通信有两种方式,一种是模块间的函数调用,另一种是模块间的变量访问。
函数访问需要知道目标函数的地址,变量访问也需要知道目标变量的地址,所以这两种方式都可以归结为模块间符号的引用。

引用该符号的模块需要和定义符号的模块进行链接。组装模块的的过程就是链接。
链接过程包括:地址和空间分配,符号决议,重定位。

在程序模块main.c中使用另一个模块func.c中的函数foo()。由于在调用foo的时候要确切知道foo这个函数的地址,但每一个模块都是单独编译的,编译main.c的时候并不知道foo函数的地址,所以它暂时把调用foo的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址修正。
链接器在链接的时候,会根据所引用的符号foo,自动去相应的func.c模块查找foo的地址,然后将main.c模块中所有引用到foo的指令重新修正,让他们的目标地址为真正的foo函数的地址。
地址修正的过程也被叫做重定位(Relocation),每一个要被修正的地方叫一个重定位入口(Relocation Entry),重定位所做的就是给程序中每个这样的绝对地址引用的位置 “打补丁”,使他们指向正确的地址。

将多个目标文件 .o 以及所需的库文件链接成最终的可执行文件

参考:
预处理器的相关知识
linux下的库文件
C++语言编译链接过程
静态库与动态库
静态库与动态库
程序员的自我修养 ——链接、装载与库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值