(1)翻译环境与运行环境

1、翻译环境
在这个环境中源代码被转换为可执⾏的机器指令(⼆进制指令)
具体是由两个过程组成:
编译:预处理、编译、汇编
链接

- 多个.c文件单独经过编译器,编译处理生成对应的目标问件
- 注:在Windows环境下的目标文件的后缀是.obj,Linux环境下的目标文件的后缀是.o
- 多个目标文件和链接库一起经过链接器处理生成最终的可执行程序
- 链接库是指运行时库(它是支持程序运行的基本函数集合)或第三方库
如果把编译器展开成3个过程:

2、预编译

预处理阶段主要处理源文件中#开始的预编译指令,规则如下
- 将所有的#define删除,并展开所有的宏定义
- 处理所有的条件编译指令,如:#if、#ifdef、#elif、#else、#endif
- 处理#include预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件
- 删除所有注释
- 添加行号和文件名标识,方便后续编译器生成调试信息等
- 保留所有的#prama的编译器指令,编译器后续会使用
经过预处理后的 .i ⽂件中不再包含宏定义,因为宏已经被展开。并且包含的头⽂件都被插⼊到 .i ⽂件中。所以当我们⽆法知道宏定义或者头⽂件是否包含正确的时候,可以查看预处理后的 .i ⽂件来确认
3、编译

- 词法分析
将源代码程序被输入扫描器,扫描器的任务就是简单的进行词法分析,把代码中的字符分割成一系列的记号(关键字、标识符、字面量、特殊字符等)
下面程序进行词法分析会得到:


- 语法分析
语法分析器,将对扫描产生的记号进行语法分析,从而产生语法树;这些语法树是以表达式位为接点的树

- 语义分析
由语义分析器来完成语义分析,即对表达式的语法层面分析;编译器能做的分析是语义的静态分析;静态语义分析通常包括声明和类型的匹配,类型的转换等等;这个阶段会报道错误的语法信息

- 汇编

4、链接


我们已经知道,每个源⽂件都是单独经过编译器处理⽣成对应的⽬标⽂件
test.c 经过编译器处理⽣成test.o
add.c 经过编译器处理⽣成add.o
我们在 test.c 的⽂件中使⽤了 add.c ⽂件中的 Add 函数和 g_val 变量。 我们在 test.c ⽂件中每⼀次使⽤ Add 函数和 g_val 的时候必须确切的知道 Add 和 g_val 的地 址,但是由于每个⽂件是单独编译的,在编译器编译 test.c 的时候并不知道 Add 函数和 g_val 变量的地址,所以暂时把调⽤ Add 的指令的⽬标地址和 g_val 的地址搁置。等待最后链接的时候由 链接器根据引⽤的符号 Add 在其他模块中查找 Add 函数的地址,然后将 test.c 中所有引⽤到 Add 的指令重新修正,让他们的⽬标地址为真正的 Add 函数的地址,对于全局变量 g_val 也是类 似的⽅法来修正地址。这个地址修正的过程也被叫做:重定位
(2)运行环境

(3)预处理
1、预定义符号
在预处理内,直接被替换

2、#define定义常量

name:名字
stuff:内容

3、#define定义宏

其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的⼀部分

提示:所以⽤于对数值表达式进⾏求值的宏定义都应该⽤这种⽅式加上括号,避免在使⽤宏时由于参数中的 操作符或邻近操作符之间不可预料的相互作⽤
4、带有副作用的宏参数
当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可 能出现危险,导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果

5、宏替换规则
在程序中扩展#define定义符号和宏时,需要涉及⼏个步骤。
1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换
2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换
3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程
注意:
1. 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
6、宏与函数的对比

为什么不用函数完成这个任务?
- ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐ 函数在程序的规模和速度⽅⾯更胜⼀筹
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数是类型⽆关的

7、#和##
- #运算符

- ##运算符
## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符
## 被称 为记号粘合 这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的
在实际开发过程中##使⽤的很少,很难取出⾮常贴切的例⼦
8、命名约定

9、#undef

10、命令行定义

11、条件编译
在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。 因为我们有条件编译指令


- 常见的条件编译指令
- 常量表达式由预处理器求值
- 多个分支条件编译
- 判断是否被定义
- 嵌套指令
…………
12、头文件的包含
1、本地文件的包含

查找策略:
先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查 找头⽂件。 如果找不到就提⽰编译错误

2、库文件的包含
查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。 这样是不是可以说,对于库⽂件也可以使⽤ “” 的形式包含? 答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件了
3、嵌套文件编译

13、其他预处理指令

——编译、链接和预处理&spm=1001.2101.3001.5002&articleId=147504173&d=1&t=3&u=0537ecf747814f1d8d3c117f07b47586)
6613

被折叠的 条评论
为什么被折叠?



