开发环境与平台选择
TODO
IDE 不是编译器!
TODO
编译器是?
编译器是将源代码 (.cpp
) 编译成可执行程序 (.exe
) 的工具。
C++ 是编译型语言,源代码不能直接执行哦!刚开始学编程的小彭老师曾经把网上的 “Hello, World” 代码拷贝到
.c
源码文件中,然后把后缀名改成.exe
,发现这样根本执行不了……后来才知道需要通过一种叫做编译器编译.c
文件,才能得到计算机可以直接执行的.exe
文件。
C++ 源码 .cpp
是写给人类看的!计算机并不认识,计算机只认识二进制的机器码。要把 C++ 源码转换为计算机可以执行的机器码。
编译器御三家
最常见的编译器有:GCC、Clang、MSVC
俗称“御三家”。
这些编译器都支持了大部分 C++20 标准和小部分 C++23 标准,而 C++17 标准都是完全支持的。
有人说过:“如果你不知道一个人是用的什么编译器,那么你可以猜他用的是 GCC。”
- GCC 主要只在 Linux 和 MacOS 等 Unix 类系统可用,不支持 Windows 系统。但是 GCC 有着大量好用的扩展功能,例如大名鼎鼎的
pbds
(基于策略的数据结构),还有各种__attribute__
,各种__builtin_
系列函数。不过随着新标准的出台,很多原本属于 GCC 的功能都成了标准的一部分,例如__attribute__((warn_unused))
变成了标准的[[nodiscard]]
,__builtin_clz
变成了标准的std::countl_zero
,__VA_OPT__
名字都没变就进了 C++20 标准。
PBDS 又称 “平板电视”
-
也有 MinGW 这样的魔改版 GCC 编译器,把 GCC 移植到了 Windows 系统上,同时也能用 GCC 的一些特性。不过 MinGW 最近已经停止更新,最新的 GCC Windows 移植版由 MinGW-w64 继续维护。
-
Clang 是跨平台的编译器,支持大多数主流平台,包括操作系统界的御三家:Linux、MacOS、Windows。Clang 支持了很大一部分 GCC 特性和部分 MSVC 特性。其所属的 LLVM 项目更是编译器领域的中流砥柱,不仅支持 C、C++、Objective-C、Fortran 等,Rust 和 Swift 等语言也是基于 LLVM 后端编译的,不仅如此,还有很多显卡厂商的 OpenGL 驱动也是基于 LLVM 实现编译的。并且 Clang 身兼数职,不仅可以编译,还支持静态分析。许多 IDE 常见的语言服务协议 (LSP) 就是基于 Clang 的服务版————Clangd 实现的 (例如你可以按 Ctrl 点击,跳转到函数定义,这样的功能就是 IDE 通过调用 Clangd 的 LSP 接口实现)。不过 Clang 的性能优化比较激进,虽然有助于性能提升,如果你不小心犯了未定义行为,Clang 可能优化出匪夷所思的结果,如果你要实验未定义行为,Clang 是最擅长复现的。且 Clang 对一些 C++ 新标准特性支持相对较慢,没有 GCC 和 MSVC 那么上心。
例如 C++20 早已允许 lambda 表达式捕获 structural-binding 变量,而 Clang 至今还没有支持,尽管 Clang 已经支持了很多其他 C++20 特性。
- Apple Clang 是苹果公司自己魔改的 Clang 版本,只在 MacOS 系统上可用,支持 Objective-C 和 Swift 语言。但是版本较官方 Clang 落后一些,很多新特性都没有跟进,基本上只有专门伺候苹果的开发者会用。
GCC 和 Clang 也支持 Objective-C。
-
MSVC 是 Windows 限定的编译器,提供了很多 MSVC 特有的扩展。也有人在 Clang 上魔改出了 MSVC 兼容模式,兼顾 Clang 特性的同时,支持了 MSVC 的一些特性(例如
__declspec
),可以编译用了 MSVC 特性的代码,即clang-cl
,在最新的 VS2022 IDE 中也集成了clang-cl
。值得注意的是,MSVC 的优化能力是比较差的,比 GCC 和 Clang 都差,例如 MSVC 几乎总是假定所有指针 aliasing,这意味着当遇到很多指针操作的循环时,几乎没法做循环矢量化。但是也使得未定义行为不容易产生 Bug,另一方面,这也导致一些只用 MSVC 的人不知道某些写法是未定义行为。 -
Intel C++ compiler 是英特尔开发的 C++ 编译器,由于是硬件厂商开发的,特别擅长做性能优化。但由于更新较慢,基本没有更上新特性,也没什么人在用了。
最近他们又出了个 Intel DPC++ compiler,支持最新的并行编程领域特定语言 SyCL。
使用编译器编译源码
MSVC
cl.exe main.cpp
这样就可以得到可执行文件 main.exe
了。
GCC
g++ main.cpp -o main
这样就可以得到可执行文件 main
了。
Linux 系统的可执行文件并没有后缀名,所以没有
.exe
后缀。
Clang
Windows 上:
clang++.exe main.cpp -o main.exe
Linux / MacOS 上:
clang++ main.cpp -o main
编译器选项
编译器选项是用来控制编译器的行为的。不同的编译器有不同的选项,语法有微妙的不同,但大致功效相同。
例如当我们说“编译这个源码时,我用了 GCC 编译器,-O3
和 -std=c++20
选项”,说的就是把这些选项加到了 g++
的命令行参数中:
g++ -O3 -std=c++20 main.cpp -o main
其中 Clang 和 GCC 的编译器选项有很大交集。而 MSVC 基本自成一派。
Clang 和 GCC 的选项都是 -xxx
的形式,MSVC 的选项是 /xxx
的形式。
常见的编译器选项有:
C++ 标准
指定要选用的 C++ 标准。
Clang 和 GCC:-std=c++98
、-std=c++03
、-std=c++11
、-std=c++14
、-std=c++17
、-std=c++20
、-std=c++23
MSVC:/std:c++98
、/std:c++11
、/std:c++14
、/std:c++17
、/std:c++20
、/std:c++latest
例如要编译一个 C++20 源码文件,分别用 GCC、Clang、MSVC:
GCC(Linux):
g++ -std=c++20 main.cpp -o main
Clang(Linux):
clang++ -std=c++20 main.cpp -o main
MSVC(Windows):
cl.exe /std:c++20 /c main.cpp
优化等级
Clang 和 GCC:-O0
、-O1
、-O2
、-O3
、-Ofast
、-Os
、-Oz
、-Og
-O0
:不进行任何优化,编译速度最快,忠实复刻你写的代码,未定义行为不容易产生诡异的结果,一般用于开发人员内部调试阶段。-O1
:最基本的优化,会把一些简单的死代码(编译器检测到的不可抵达代码)删除,去掉没有用的变量,把部分变量用寄存器代替等,编译速度较快,执行速度也比-O0
快。但是会丢失函数的行号信息,影响诸如 gdb 等调试,如需快速调试可以用-Og
选项。-O2
:比-O1
更强的优化,会把一些循环展开,把一些函数内联,减少函数调用,把一些简单的数组操作用更快的指令替代等,执行速度更快。-O3
:比-O2
更激进的优化,会把一些复杂的循环用 SIMD 矢量指令优化加速,把一些复杂的数组操作用更快的指令替代等。性能提升很大,但是如果你的程序有未定义行为,可能会导致一些 Bug。如果你的代码没有未定义行为则绝不会有问题,对自己的代码质量有自信就可以放心开,编译速度也会很慢,一般用于程序最终成品发布阶段。-Ofast
:在-O3
的基础上,进一步对浮点数的运算进行更深层次的优化,但是可能会导致一些浮点数计算结果不准确。如果你的代码不涉及到 NaN 和 Inf 的处理,那么-Ofast
不会有太大的问题,一般用于科学计算领域的终极性能优化。-Os
:在-O2
的基础上,专门优化代码大小,性能被当作次要需求,但是会禁止会导致可执行文件变大的优化。会把一些循环展开、内联等优化关闭,把一些代码用更小的指令实现,尽可能减小可执行文件的尺寸,比-O0
、-O1
、-O2
都要小,通常用于需要节省内存的嵌入式系统开发。-Oz
:在-Os
的基础上,进一步把代码压缩,可能把本可以一条大指令完成的任务也拆成多条小指令,为了缩小尺寸牺牲运行时性能,大幅减少了函数内联的机会,有时用于嵌入式系统开发。-Og
:在-O0
的基础上,尽可能保留更多调试信息,不做破坏函数行号等信息的优化,建议配合产生更多调试信息的-g
选项使用。但还是会做一些简单的优化,比-O0
执行速度更快。但-Og
的所有优化都不会涉及到未定义行为,因此非常适合调试未定义行为。但是由于插入了调试信息,最终的可执行文件会变得很大,一般在开发人员调试时使用。
MSVC:/Od
、/O1
、/O2
、/Ox
、/Ob1
、/Ob2
、/Os
/Od
:不进行任何优化,忠实复刻你写的代码,未定义行为不容易产生诡异的结果,一般用于调试阶段。/O1
:最基本的优化,会把一些简单的死代码删除,去掉没有用的变量,把变量用寄存器代替等。/O2
:比/O1
更强的优化,会把一些循环展开,把一些函数内联,减少函数调用,还会尝试把一些循环矢量化,把一些简单的数组操作用更快的指令替代等。一般用于发布阶段。/Ox
:在/O2
的基础上,进一步优化,但是不会导致未定义行为,一般用于发布阶段。/Ob1
:启用函数内联。/Ob2
:启用函数内联,但是会扩大内联范围,一般比/Ob1
更快,但是也会导致可执行文件变大。/Os
:在/O2
的基础上,专门优化代码大小,性能被当作次要需求,但是会禁止会导致可执行文件变大的优化。会把一些循环展开、内联等优化关闭,把一些代码用更小的指令实现,尽可能减小可执行文件的尺寸,通常用于需要节省内存的嵌入式系统开发。
注意:函数内联是一种优化策略,和
inline
关键字毫无关系,请看稍后推出的符号链接专题课程或抢先看小彭老师视频了解inline
,以后的性能优化专题课程也会介绍函数内联优化的成功案例。
调试信息
Clang 和 GCC:-g
、-g0
、-g1
、-g2
、-g3
MSVC:/Z7
、/Zi
头文件搜索路径
指定要链接的库
库文件搜索路径
定义宏
Clang 和 GCC:-Dmacro=value
MSVC:/Dmacro=value
例如:
警告开关
标准库御三家
-
libstdc++ 是 GCC 官方的 C++ 标准库实现,由于 GCC 是 Linux 系统的主流编译器,所以 libstdc++ 也是 Linux 上最常用的标准库。你可以在这里看到他的源码:https://github.com/gcc-mirror/gcc/tree/master/libstdc%2B%2B-v3
-
libc++ 是 Clang 官方编写的 C++ 标准库实现,由于 Clang 是 MacOS 系统的主流编译器,所以 libc++ 也是 MacOS 上最常用的标准库。libc++ 也是 C++ 标准库中最早实现 C++11 标准的。项目的开源地址是:https://github.com/llvm/llvm-project/tree/main/libcxx
-
MSVC STL 是 MSVC 官方的 C++ 标准库实现,由于 MSVC 是 Windows 系统的主流编译器,所以 MSVC STL 也是 Windows 上最常用的标准库。MSVC STL 也是 C++ 标准库中最晚实现 C++11 标准的,但是现在他已经完全支持 C++20,并且也完全开源了:https://github.com/microsoft/STL
值得注意的是,标准库和编译器并不是绑定的,例如 Clang 可以用 libstdc++ 或 MSVC STL,GCC 也可以被配置使用 libc++。
在 Linux 系统中,Clang 默认用的就是 libstdc++。需要为 Clang 指定 -stdlib=libc++
选项,才能使用。
牛头人笑话:“如果你不知道一个人是用的什么标准库,那么你可以猜他用的是 libstdc++。因为即使他的编译器是 Clang,他用的大概率依然是 libstdc++。”
标准库的调试模式
TODO
C++11 ABI 问题
在一些特别古老的发行版上(比如 Ubuntu 16.04、CentOS),他们的标准库不支持 C++11,可以开启这个宏:
#define _GLIBCXX_USE_CXX11_ABI 0
或者命令行选项 -D_GLIBCXX_USE_CXX11_ABI=0
。
为了更好的学习现代 C++,还是建议安装新的发行版。