知识的学习在于点滴记录,坚持不懈;知识的学习要有深度和广度,不能只流于表面,坐井观天;知识要善于总结,不仅能够理解,更知道如何表达!
编译
我们以hello.c举例子
#include<stdio.h>
//这是一些注释
int main(){
printf("Hello World\n");
return 0;
}
预编译(预处理)
gcc -E hello.c -o hello.i
这一阶段,主要处理哪些带"#"的,也会删除注释
- #include的头文件,直接进行展开
- #define删除,展开宏定义(直接替换,这阶段没有编译器介入,不做类型安全检查)
- 处理条件预编译指令#if、#ifdef、#elif、#else、#endif
- 添加行号和文件名标识,便于编译器编译过程打印错误或警告的行号
- 保留#pragma,编译器需要
编译
gcc -S hello.i -o hello.s
编译过程总的讲就是做词法分析、语法分析、语义分析、及优化后生成相应的汇编代码文件。
//example
array[index] = (index + 4) * (2 + 6)
汇编
gcc -c hello.s -o hello.o
汇编器输出的是.o目标文件
汇编器将 编译器编译后生成的 汇编代码 翻译成机器可以执行的指令,也仅仅是根据汇编指令和机器指令的对应表一一翻译。
链接
编译器做了些什么
词法分析(扫描)
这一个阶段很好理解,就是将源代码的字符序列分割成一系列记号Token,比如上面程序在词法分析后生成了16个记号,下面部分列出
记号 | 类型 |
---|---|
array | 标识符 |
[ | 左方括号 |
] | 右方括号 |
index | 标识符 |
在识别记号的同时,扫描器还做了其他事,比如将标识符放到符号表,数字、字符串常量存放到文字表等,以备后续使用
语法分析
这一步将产生语法树
记号被存放在一个树的数据结构上
语义分析
这一阶段只对表达式的语法层面进行分析,但他不了解语句是否有意义,比如两个指针做乘法运算,这被判定为合法的;
编译器只是分析静态语义,(动态语义是在运行期才能确定的语义);
静态语义包括:声明和类型的匹配、类型转换。
语法树变成这样,可以看到每个表达式(包括符号数字)都被标识了类型。
中间语言生成
这里存在一个优化的操作,比如2+6会被直接优化成8,但这一步直接在树上操作是有难度的,因此优化器会将整个语法树转换成中间代码;
汇编代码生成
将中间代码翻译成汇编代码,然后对汇编代码做一些优化
链接器
我们把每个源代码的模块独立编译,然后按照需要组装起来,组装过程就是链接
链接器做的一些工作就是去定位符号,比如一个函数,他在编辑器中可以写在上面、也可以写在下面,一个变量可以定义在当前变量引用,也可以定义在别的变量引用,这些定位的操作就是链接器的作用,被称为重定位;
静态链接和动态链接
大家可以看看这篇博客的讲解
静态链接、动态链接
静态链接优缺点:
-
优点快
-
缺点:浪费空间,如果多个目标程序对同一目标文件都有依赖,那么目标文件会在内存存在多个副本
-
缺点:更新困难,库函数如果有函数或者代码修改,我们就需要重新编译链接形成新的可执行文件
动态链接优缺点
- 缺点:慢,链接过程推迟到了程序每次运行时期,每次运行都需要链接
- 优点:多个程序共享同一份目标文件的副本,避免空间浪费
- 优点:更新容易,程序下一次运行,新版本的目标文件就会被加载到内存链接起来,完成更新;
如果这篇文章有帮助到你希望留下一个赞,文中如果有不对的地方也欢迎大佬指出!