如果你是一名newbird的话,建议观看如下视频加深你的理解,再看如下内容:
https://www.bilibili.com/video/BV1N24y1B7nQ?p=7
The cherno会额外告诉你如何将目标文件转换成汇编代码,CPU执行指令的过程以及编译器如何通过删除冗余变量,进行优化等知识。
声明:
以下内容均是chatGpt生成与对该视频总结获得的,希望对大家有所帮助。
什么是C++编译器
C++编译器是将C++源代码转换为可执行程序的软件工具,比如最出名的Visual Studio。
工作原理
其工作原理可以分为三个主要阶段:预处理、编译和链接。
1. 预处理
预处理阶段处理源代码中的预处理指令,如#include
和#define
等,并将它们替换为源代码。
预处理器还可以执行条件编译,根据代码中定义的条件来包含或排除代码。处理完成后,生成经过预处理的源代码。
2. 编译
编译阶段将预处理的源代码转换为中间代码,包括生成抽象语法树等操作。
编译器对代码进行词法分析和语法分析,并对代码进行语义检查,以确保其符合C++语言规范。然后,编译器将中间代码转换为机器代码,生成目标文件。
2.1 什么是中间代码?
C++编译器在编译阶段会将预处理后的源代码转换为中间代码,也称为目标代码(Object Code)。
特性:
这些中间代码是与平台无关的低级代码,通常是二进制格式或汇编代码。
具体来说,编译器会将源代码转换为抽象语法树(AST)。
2.2 什么是AST
概念:
AST是编译器在编译过程中使用的一种数据结构,用于表示源代码的语法结构。
编译器会对AST进行一系列的优化和转换,以生成目标代码。这些优化包括删除冗余代码、提取公共子表达式、常量折叠等。
生成的中间代码是与平台无关的,因为它们没有针对特定的CPU架构进行优化。在链接阶段,链接器将这些目标文件合并成一个可执行文件,并将其与操作系统和CPU架构相关的库文件链接起来,生成最终的可执行文件。
3. 链接
链接阶段将多个目标文件和库文件合并成一个可执行文件。
链接器会解析代码中的符号,找到其定义并将其连接起来。这些符号可能来自其他目标文件或库文件。
3.1 具体例子
假设我们有两个C++源代码文件,一个是main.cpp
,一个是hello.cpp
。main.cpp
调用了hello.cpp
中的一个函数,需要将它们链接起来才能生成可执行文件。
现在main.cpp
,内容如下:
#include <iostream>
#include "hello.h"
int main() {
hello();
return 0;
}
另一个是hello.cpp
,内容如下:
#include <iostream>
#include "hello.h"
void hello() {
std::cout << "Hello, world!" << std::endl;
}
还有一个头文件hello.h
,内容如下:
#ifndef HELLO_H
#define HELLO_H
void hello();
#endif
当我们运行时,会按如下的命令编译这些代码:
$ g++ -c main.cpp
$ g++ -c hello.cpp
$ g++ -o hello main.o hello.o
第一条命令将main.cpp
编译为main.o
目标文件,第二条命令将hello.cpp
编译为hello.o
目标文件,最后一条命令将两个目标文件链接起来,生成可执行文件hello
。
我们可以执行./hello
命令来运行程序,结果应该输出"Hello, world!"
(ChatGpt说的,我没测试,但是逻辑很合理的样子)。
可以看到在链接阶段,链接器将main.o
和hello.o
文件合并为一个可执行文件。首先,链接器会对目标文件进行符号解析,找到main.o
中调用hello.cpp
函数的符号引用,并在hello.o
中找到符号定义。然后,链接器将引用和定义链接起来,生成可执行文件。
3.2 额外问题(符号冲突的问题)
概念:
链接器还需要解决符号冲突的问题。当多个目标文件中存在相同的符号定义时,链接器会报告错误,因为无法判断应该使用哪个定义。
解决方案:
为了解决这个问题,C++提供了一些机制。
- 在头文件中声明函数或变量为
extern
,在链接阶段不进行符号解析,而是在运行时再进行解析。 - 此外,链接器还可以使用静态库或动态库来解决符号冲突问题。静态库在链接阶段被直接合并到可执行文件中,而动态库在运行时加载到内存中。
4. 总结
最终生成的可执行文件可以在计算机上运行,执行程序所描述的操作。
总的来说,C++编译器的工作原理是将源代码转换为可执行文件的过程,通过预处理、编译和链接三个阶段实现。