cpp 编译过程
一、预编译
该过程主要是根据预处理指令(#include、#define、#if等)重新组装c++代码,经过预处理阶段,将产生一个没有注释、没有include、没有define、没有条件编译(#if/#else)等指令的.i
文件
可以使用 g++ -E main.cpp -o main.i
生成 .i
文件
将
include
的头文件拼接进来将
define
宏替换将
if
等条件替换保留#pragma编译器指令(非预编译指令),该指令用于设置编译器状态或指示编译器完成一些特定的动作
在预编译阶段被处理的指令亦称伪指令(如#include、#define、#if等)
常见的#pragma用法有#pragma once、#pragma message等用#line指令(非预编译指令)强制编译器按指定的行号对源程序的代码重新编号,以便于编译时产生的错误警告能显示行号
#line的用法一般为 #line 行号 filename,用于强制编译器按指定的行号,开始对源程序的代码重新编号,在调试的时候,可以按此规定输出错误代码的准确位置
经过这一阶段的处理,源程序就就变成了只包含字符串和#pragma和#line的文件
1 |
|
1 | # 82 "D:/mingw64-13.1.0/lib/gcc/x86_64-w64-mingw32/13.1.0/include/c++/iostream" 3 |
二、编译
编译原理:
词法分析
语法分析
抽象语法树(AST)
语义分析
符号表
符号表用来存储程序中各个变量的相关信息,如类型,作用域,访问控制信息,是一种key-value结构,数据结构可以是哈希表(查找快),也可以是红黑树(省空间)
符号表处理作用域的方法:一是用一张表,进入作用域就插入元素,离开作用域就删除元素,二是用多张表,构成一个栈,进入作用域就插入一张新表,离开就删除栈顶的表
符号表处理名字空间的方法:引入标签,标号来区别不同类型
汇编代码
源程序经过了词法分析、语法分析和语义分析,最终生成汇编代码,生成了
*.s*
文件
g++ -S main.cpp -o main.s
三、汇编
将汇编代码转为机器码。
编译阶段得到的汇编代码并不能直接被计算机识别,计算机的元件(寄存器、cpu等)都是由集成电路实现(与非门、或非门),这些电路组成了复杂的逻辑,每个元件由输入引脚和输出引脚组成,通过输入电流与否来得到输出结果,每个引脚通过0或1来表示是否输入电流,0或1按照一定的规则就组成了机器指令。所以要想计算机理解我们的源代码,汇编指令还得翻译成机器指令,汇编指令只是机器指令的一种助记符(只是给机器指令命了名),它是用来帮助人们更好的理解机器指令而产生的,几乎每一条汇编指令对应一条机器指令。
汇编阶段通过汇编器把前面得到的汇编代码翻译成目标文件,生成.obj或.o目标文件,该文件中存放的就是与源代码等效的机器指令,每一个.cpp文件都会对应生成一个.obj或.o文件
g++ -C *.cpp -o *.o
四、链接
目标文件并不能直接执行,还需要经过链接过程,原因是:
某个.cpp文件调用了另外.cpp文件中的函数或者常量等,它们是相互独立的(每个.cpp对应一个.obj文件),为了解决这类问题,必须要将调用者目标文件与被调用者的目标文件链接起来,最终得到可执行程序(.exe或.elf等)
链接一般分为静态链接和动态链接
g++ *.cpp -o *.exe