C++底层的编译逻辑和过程

C++底层的编译逻辑和过程

C++底层的编译逻辑和过程

Aurelia_Veil

·

2025-04-23 23:23:32

·

科技·工程

C++ 底层的编译逻辑和过程:

你是否好奇 C++ 是怎么编译的咩,你是否会觉得一个文本就能让代码变成一个个指令运行起来的,这篇文章告诉你 C++ 的底层编译逻辑和过程。

一、预处理

在我们的编译开始前,预处理器会把代码当做文本进行处理,解析所有需要预处理的指令,这个指令就是以 # 开头的代码,然后生成一个中间代码文件(后缀名为 .i 或者 .ii)。

1. 宏展开

在如下代码中:

#define int long long

#define maxl(a,b) a>b?a:b

这些指令就会替换与前向匹配的指定文本为后项的替换文本,但可能会出现如重复计算的副作用,但是在后面的编译器中会继续优化。

2. 头文件

在如下代码中:

#include

预处理器会搜索标准库路径,将 iostream 的内容替换并复制到这里。

3. 条件指令

在如下代码中:

# ifdef DEBUG

printf("debug\n");

# endif

如果没有定义 DEBUG 则这段代码会被移出,就消失不见了。

二、编译

注:此部分因过于专业,本人也收集了来自网络的知识,所以会出现专业词汇。

在经过预处理器的代码会进入编译器前段,通过词法、语法、语义分析生成中间表示,然后转换成目标平台的汇编代码(后缀名为 .s)。

1. 词法分析

编译器将字符流(例如 int x=114514;)拆分成各种词素,包括关键字、标识符、运算符、等等。例如:

[Keyword:int] [Identifier:x] [Operator:=] [Literal:114514] [Separator:;]

2. 语法分析

根据 C++ 的语法规则,将各种词素组织成抽象语法树。例如:int x=114514; 会被解析成一个包含类型、变量名和初始值变量声明节点。

3. 语义分析

检查抽象语法树语义的正确性,例如类型的匹配、作用域的规则。对于 C++ 这特有的特性(例如模版、重载),会进行名称修饰,将函数的签名编码作为唯一符号,例如:

void foo(int)

void foo(double)

会变成:

_Z3fooi

_Z3food

4. 中间代码的生成与优化

编译器将抽象语法树转换为 LLVM IR 或 GIMPLE(GCC 的中间表示),并进行优化。例如消除冗余计算、循环优化等,就比如如下代码:

int sum=0;

for(int i=1;i<=100;i++){

sum+=i;

}

会被优化替换成:

int sum=5050;

5. 目标代码生成

根据目标平台的指令集架构,将 IR 转换为汇编代码。例如(asm 语言):

mov eax,0 ;sum=0

mov ecx,0 ;i=0

loop:

inc ecx ;i++

add eax,ecx ;sum+=i

cmp ecx,100

jl loop

三、汇编,生成机器指令

汇编器会将汇编代码转换成目标文件(后缀名为 .o 或 .obj),包括机器码和符号表。

1. 二进制编码:

汇编语言被转换成操作码,例如,add eax,ecx 对应二进制编码为 0x01C8。

2. 符号表

记录全局变量、函数的地址和类型,例如:

_main: address 0x100, type TEXT

_sum: address 0x200, type DATA

3. 重定位信息

标记需要链接阶段处理的地址。比如 main 函数调用了外部函数 check,则 call check 的地址会被标记为待填充。

四、链接

链接器(如 ld)会将多个目标文件和库合并成最终的可执行文件或动态库。

1. 符号解析

链接器检查所有的目标文件,确保每个符号(如函数、全局变量)均有定义。

2. 地址分配和重定位

给所有符号分配出最终的内存地址,并修改代码中的引用部分。例如,若 main 函数被分配到地址 0x400000,则所有调用 main 的地方都会更新为这一个地址。

3. 静态库和动态库的处理

静态链接:将库代码直接复制转移到可执行文件中,生成独立的二进制文件。

动态链接:有且仅有在运行时加载共享库(如后缀名为 .so 或者 .dll),通过 PLT 来延迟绑定。

4. 生成可执行文件格式

链接器根据系统格式(如 ELF、PE)生成最终文件,包含代码段(后缀名为 .text)、数据段(后缀名为 .data)、BSS 段和调试信息。

五、C++ 编译的特殊性

1. 模版实例化

模板代码在编译过程中会生成多个实例(如 vector 和 vector),可能会导致代码膨胀,但现代编译器会重复合并进行优化。

2. 异常的处理

try 和 catch 语句会生成额外的异常表,记录代码位置与处理函数之间的映射关系。

六、专业名词解析

注:本部分结合了一部分网络收集内容。

1. 词素(Tokens)

词素是在编译过程中词法的分析阶段,它会将源代码字符流分解成的最小的有意义的单元。它们是构建抽象语法树的基础。

(1) 词素的分类

词素的类型

示例

说明

关键字

int,for

语言预定义的保留字,具有固定的含义

标识符

main,ans,cnt

用户自己定义的名称,但要符合命名规则

字面量

114514,"abcdefg",3.1415926,false

直接表示的常量值(不一定是整型)

运算符

+,>,!=,new

由单字符或多字符组合,表示运算和操作(算术、逻辑、内存操作)

分隔符

(,[,;,{

标记代码块、参数列表或语句结束

预处理指令

#include,#define

仅在预处理阶段处理的指令

注释

//,/* */

通常被词法分析器忽略的语句,不生成有效词素

(2) 词素的生成过程

词素由语法分析器生成,核心步骤如下:

输入(字符流):

源代码被读取为字符序列。

正则表达式匹配:

通过预定义的正则表达式规则识别词素类型:

关键字:\b(int|return|if|else)\b。

标识符:[a-zA-Z_][a-zA-Z0-9_]*。

整数字面量:\d+。

运算符:(\+|\-|\*|/|=|==|&&)。

有限自动机处理:

词法分析器实现为有限状态机,逐字符处理输入:

状态转移:根据当前字符切换状态(如从“初始状态” 进入“数字识别状态”)。

最长匹配原则:优先匹配更长的有效的词素(如 ++ 视为单个运算符,而非两个 +)。

生成词素流:

输出格式化的词素序列,每个词素包含:

类型:表示词素类型。

值:原始字符串内容。

位置(行、列):用于错误提示。

示例:int main() { return 0; } 生成的词素流:

[KEYWORD:int] (Line 1, Col 1)

[IDENTIFIER:main] (Line 1, Col 5)

[LPAREN:] (Line 1, Col 9)

[RPAREN:] (Line 1, Col 10)

[LBRACE:] (Line 1, Col 12)

[KEYWORD:return] (Line 1, Col 14)

[LITERAL:0] (Line 1, Col 21)

[SEMICOLON:] (Line 1, Col 22)

[RBRACE:] (Line 1, Col 24)

(3) 词素处理的问题

消除歧义:

最长匹配:如:a+++++b 会被解析为 a ++ ++ + b 而不是 a ++ + ++ b。

优先级规则:某些符号需要根据上下文确定类型(一字多义)。

上下文相关词素:

C++ 模版:vector>g 中的 >> 应识别的是两个 > 而不是右移运算符

C/C++ 类型修饰符:const 在变量声明和函数参数中有着不同的作用。

2. 抽象语法树(Abstract Syntax Tree)

抽象语法树是编译器和解释器之中的核心数据结构,它将源代码的结构呈树状形式抽象表示,是高级语言与机器代码之间的过渡。

(1) 抽象语法树于解析树的区别

解析树:完全保留了所有语法的细节(分号、括号等),严格按照对应语法规则推导过程。

抽象语法树:仅仅保留程序逻辑结构的必要元素,会过滤掉冗余语法符号部分。

举个例子,在表达式 1*((1+1-4)/5) 中,解析树会包含括号结点,而抽象语法树是直接以树的形式体现运算优先级。

(2) 抽象语法树如何生成

我们以 int main(){return 2*3+4;} 为例。

词法分析:

[Keyword:int] [Identifier:main] [LParen] [RParen] [LBrace]

[Keyword:return] [Literal:2] [Operator:*] [Literal:3]

[Operator:+] [Literal:4] [Semicolon] [RBrace]

语法分析:

递归下降解析器根据 C++ 语法规则构建解析树。

关键语法规则:

FunctionDecl → Type Identifier Params CompoundStmt

CompoundStmt → '{' Stmt* '}'

Stmt → ReturnStmt Expr ';'

Expr → Expr '+' Expr | Expr '*' Expr | Literal

抽象语法树转换:

简化后如下:

FunctionDecl

├─ Type: int

├─ Name: main

└─ CompoundStmt

└─ ReturnStmt

└─ BinaryOperator(op=+)

├─ BinaryOperator(op=*)

│ ├─ IntegerLiteral(2)

│ └─ IntegerLiteral(3)

└─ IntegerLiteral(4)

(3) 抽象语法树的优化

常量直接赋值:

提前计算所有常量表达式。

如:2*3+4 会直接计算赋值为 10。

优化前:

ReturnStmt

└─ BinaryOperator(+)

├─ BinaryOperator(*)

│ ├─ 2

│ └─ 3

└─ 4

优化后:

ReturnStmt

└─ IntegerLiteral(10)

消除无作用代码:

删除无作用的分支(if(false){})等无效结构。

到这里,此文章就结束了,感谢观看到最后的朋友们咩,可以给东东羊一个赞,给个关注吗,谢谢咩!

🎀 相关推荐

小米云服务使用手册
365bet官方投注网址

小米云服务使用手册

📅 07-03 👀 9220
使用“铅笔”、“线条”、“弧形”或“自由绘制”工具绘制线条和形状
队报球迷评世界杯最佳阵:梅西姆巴佩莫德里奇领衔,萨卡恩佐在列