编译
我们平常说的编译其实是一个广义的概念。具体来说C/C++的编译过程包含了四个步骤:
- 预处理(Preprocessing)
- 编译(Compilation)
- 汇编(Assemble)
- 链接(Linking)
预处理
预处理是在编译之前的一个步骤,主要是处理以#
开头的预处理指令。预处理器会根据这些指令修改源文件,比如#include
会将头文件的内容插入到源文件中,#define
会将宏定义替换为对应的内容等,#ifdef
、#ifndef
、#if
、#else
、#endif
等用于条件编译。
例如:
#define PI 3.1415926
#define E 2.71828
#define max(a, b) ((a) > (b) ? (a) : (b))
float foo() {
float r = max(PI, E);
return r;
}
经过预处理后:
float foo() {
float r = ((3.1415926) > (2.71828) ? (3.1415926) : (2.71828));
return r;
}
预处理的结果是一个没有预处理指令的源文件。
编译
编译是将预处理后的源文件转换为汇编代码的过程。编译器会将源文件翻译成汇编代码。有关汇编代码的知识会在计算机组成原理课程中详细讲解。
编译阶段会创建一个符号表,用于存储源代码中定义的变量、常量、类型和其他符号的信息。
汇编
汇编是将汇编代码转换为机器码的过程。汇编器会将汇编代码翻译成机器码,生成二进制目标文件。
此时的目标文件没有给符号表中的符号分配相应的地址,因此还不能直接运行。
链接
链接的作用是将编译器和汇编器所生成的多个目标文件(即已编译的代码文件)合并成一个可执行文件。
链接器将符号(如全局变量和函数)解析为它们的实际位置,同时也会将库文件链接到可执行文件中。最终的结果是一个可执行文件,可以被操作系统加载到内存中执行。
编译器
目前主流的编译器有3种,分别是GCC、Clang和MSVC,它们分别有自己的特性:
C++编译器 | 编译器全称 | 支持的平台 | 备注 |
---|---|---|---|
MSVC | Microsoft Visual C++ | Windows | 由微软开发,主要用于Windows平台应用程序的开发。Visual Studio系列IDE默认集成了该编译器。 |
GCC | GNU Compiler Collection | Windows, Linux, macOS | 开源编译器,支持多种平台,Linux下C++开发一般默认会使用此编译器。 |
Clang | Clang / Low Level Virtual Machine | Windows, Linux, macOS | LLVM项目的一部分,提供高效的编译性能。macOS的XCode工具默认集成了此编译器。 |
不同的编译器对C++标准有不同的实现,对C++标准的支持也有所不同,比如三者对于C++20特性支持各有差异。不同编译器也有一些自己特有的扩展,比如MSVC的scanf
函数的安全版本scanf_s
、GCC的__attribute__
等。还有例如#pragma once
预处理指令,虽然主流编译器都支持,但是并不是C++标准的一部分。
注意MSVC只作为C++的编译器,其C语言编译能力只是顺带的。
文件组织
从上述编译的介绍中我们可以发现,如果想要让一个项目编译出一个可执行文件,我们有这些需求:
- 告诉编译器源文件的位置、头文件的位置等,使其能够找到所有的依赖关系,比如找到一个
.cpp
文件中#include
的.h
头文件,然后将其内容插入到源文件中。 - 告诉编译器如何编译这些源文件,比如编译选项、编译器版本等,并且添加合适的调试信息。
- 告诉链接器如何链接这些目标文件,比如链接库文件、链接选项等,使其能够正确合并这些目标文件。
因此,我们需要一个工具来帮助我们管理这些文件,比如接下来要介绍的CMake。