目录
gcc/g++--编译器
介绍
使用格式
通用选项
编译选项
链接选项
程序编译过程
预处理(宏替换)
编译 (生成汇编)
分析树(parse tree)
编译优化
删除死代码
寄存器分配和调度
强度削弱
内联函数
生成目标代码
汇编 (生成二进制代码)
链接(生成可执行文件)
函数库
引入
介绍
动态链接
示例编辑
gcc/g++--编译器
介绍
是GNU编译器集合(GNU Compiler Collection)中的两个重要组件,用于编译和链接程序
使用格式
g++也一样
一般是:
通用选项
编译选项
eg:
链接选项
eg:
程序编译过程
预处理(宏替换)
预处理后,生成.i文件,可以通过-E选项拿到编译的中间过程中的.i文件
编译 (生成汇编)
- 从.i文件中,编译器会对其进行词法/语法/语义分析,并且会对代码进行优化
- 编译后生成.s文件,使用-S选项可以拿到这个.s文件
分析树(parse tree)
- 是一种用于表示源代码语法结构的树状数据结构,通常通过语法分析器生成
- 它反映了源代码中的各个语法元素(如关键字、运算符、变量、函数调用等)之间的嵌套关系,帮助编译器理解代码的结构并执行后续的编译步骤
- 它将源代码分解为一个个语法单元,然后根据编程语言的语法规则将这些单元组合成树状结构
编译优化
为了提高计算机程序的执行效率和性能,会进行编译优化
删除死代码
实际不会产生作用的代码:
- 执行不到的代码
- 执行的到但是没有用的代码(没有使用过的变量))
寄存器分配和调度
- 很多编译器会将 for循环的循环控制变量 调度到寄存器访问(寄存器快!)
强度削弱
- 用执行时间较短的操作(指令)去代替一个耗时操作
- 下面的代码,由于需要使用的是计算的结果,因此中间数可以被替换掉(比如*变+):
#include <iostream> using namespace std; int main() { int a1 = 5; int b1 = 17; int c1 = a1 * b1; cout << c1 << endl; // 强度削弱 int a2 = 5; int b2 = a2 << 4; int c2 = a2 + b2; cout << c2 << endl; return 0; }
内联函数
除了编译器自动做出的优化,程序员在编写的时候也需要手动进行优化(因为编译器没有我们想的那么智能啦)
内联函数(将 短小但常用的函数 定义为内联函数(inline),可以减少函数调用开销)
但其实内联函数也可以被编译器自动识别并使用
生成目标代码
- 将经过语法分析和语义分析的源代码转化为目标代码,这个目标代码可以是汇编语言、中间代码或直接的机器代码,具体取决于编译器的设计和目标平台
- 代码生成器的主要任务是将 [高级编程语言的源代码] 翻译成 [目标平台的可执行代码]
汇编 (生成二进制代码)
中间过程也有很多,就不介绍了
链接(生成可执行文件)
函数库
引入
- 我们在使用库函数时,是直接调用+引用头文件
- 但是头文件中只有函数声明,那么实现在哪里呢?
介绍
- 函数库分为静态库和动态库
- 使用静态库的方式就是静态链接,使用动态库的方式就是动态链接
- 其中,静态库内存开销大,但不需要库文件,后缀.a
- 动态库开销很小,一份库文件可以被多个程序共享使用,所以一般都会使用动态链接,后缀.so
动态链接
- 当程序运行时,动态链接器会查找并加载所需的库,根据可执行文件中的引用信息,将库文件映射到进程的地址空间
- 动态链接器解析程序中的未解析引用,将其与库中的实际函数或符号关联起来
- 程序在运行过程中,会调用库中的函数或方法
- 并且,库是在运行时加载的,因此库的更新可以在不停止程序的情况下进行
示例
- 直接编译生成的是那个test(也就是使用了gcc的动态链接)
- test_static是在编译的时候,选择了使用静态库链接生成的
- 可以看到大小差很多
总结来说,编译过程的选项是" esc ",只不过在实际使用时,只有c是小写字母,其他都是大写字母