c++ compilers on Windows -- msvc vs gcc vs clang

平时的工作大部分是在DEOS上进行嵌入式软件的开发和测试. 对于在通用操作系统上的软件开发得时不时自己磨练一下. 这篇列出几个常见的在Windows可用的C++编译器的介绍, 以及如何安装使用.

写这篇的时候是2018年9月, 我刚在电脑上安装了这几个compiler的最新版本. 分别是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ gcc --version
gcc.exe (Rev1, Built by MSYS2 project) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

C:\home>clang --version
clang version 6.0.1 (tags/RELEASE_601/final)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

C:\home>cl -?
Microsoft (R) C/C++ Optimizing Compiler Version 19.15.26729 for x64

VC++

毕竟是主场, 先介绍原生的微软编译器, Microsoft Visual C++, 简称msvc. 过去微软免费的Visual C++编译器都是通过Visual Studio Express版本安装的, 从2017版改了个名字叫 Visual Studio Community 2017.

VC的安装只需要到官网下载安装包, 然后按指引安装. 下面是我安装的选项和安装地址.

这里重点想说的是如何不通过IDE环境编译, 直接使用编译器链接器来进行编译. 跳过IDE, 有利于学习中了解编译器的细节.

安装完官方的安装包之后, 可以直接点开开始菜单里下面这几个快捷方式来直接打开相应的命令行下编译环境.

仔细研究一下这几个快捷方式, 发现他们都是调用一个windows的批处理文件C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat, 只是参数不同.

在进入这个批处理, 发现其背后有一大堆批处理文件帮助设置环境变量. 其中核心的批处理是C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat. 可以通过看这个文件的帮助信息来了解都有哪几种类型的环境.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -help
.
** Visual Studio "15" Developer Command Prompt Help **
** Version : 15.8.4
.
Syntax: vsdevcmd.bat [options]
[options] :
-arch=architecture : Architecture for compiled binaries/libraries
** x86 [default]
** amd64
** arm
** arm64
-host_arch=architecture : Architecture of compiler binaries
** x86 [default]
** amd64
-winsdk=version : Version of Windows SDK to select.
** 10.0.xxyyzz.0 : Windows 10 SDK (e.g 10.0.10240.0)
[default : Latest Windows 10 SDK]
** 8.1 : Windows 8.1 SDK
** none : Do not setup Windows SDK variables.
For use with build systems that prefer to
determine Windows SDK version independently.
-app_platform=platform : Application Platform Target Type.
** Desktop : Classic Win32 Apps [default]
** UWP : Universal Windows Platform Apps
-no_ext : Only scripts from [VS150COMNTOOLS]\VsDevCmd\Core
directory are run during initialization.
-no_logo : Suppress printing of the developer command prompt banner.
-vcvars_ver=version : Version of VC++ Toolset to select
** [Default] : If -vcvars_ver=version is NOT specified, the toolset specified by
[VSInstallDir]\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt will be used.
** 14.0 : VS 2015 (v140) VC++ Toolset (installation of the v140 toolset is a prerequisite)
** 14.1x : VS 2017 (v141) VC++ Toolset, if that version is installed on the system under
[VSInstallDir]\VC\MSVC\Tools\[version]. Where '14.1x' specifies a partial
[version]. The latest [version] directory that matches the specified value will
be used.
** 14.1x.yyyyy : VS 2017 (v141) VC++ Toolset, if that version is installed on the system under
[VSInstallDir]\VC\MSVC\Tools\[version]. Where '14.1x.yyyyy' specifies an
exact [version] directory to be used.
-vcvars_spectre_libs=mode : version of libraries to use.
** [Default] : If -vcvars_spectre_libs=libraries is NOT specified, the project will use the normal
libraries.
** spectre : The project will use libraries compiled with spectre mitigations.
-startdir=mode : configures the current directory after (successful) initialization of the environment.
** none : the command prompt will exist in the same current directory as when invoked
** auto : the command prompt will search for [USERPROFILE]\Source and will change directory
if it exists.
** If -startdir=mode is not provided, the developer command prompt scripts will
additionally check for the [VSCMD_START_DIR] environment variable. If not specified,
the default behavior will be 'none' mode.
-test : Run smoke tests to verify environment integrity in an already-initialized command prompt.
Executing with -test will NOT modify the environment, so it must be used in a separate call
to vsdevcmd.bat (all other parameters should be the same as when the environment was
initialied)
-help : prints this help message.

注意-arch和-host_arch的含义, host_arch是指我们编译器本身是被编译为32位x86的指令还是64位amd64的指令. -arch是说编译器工作的对象是要把源码编译为那种架构, 从帮助里可以看出msvc可以支持4种, 32位和64位Intel, 32位和62位的ARM处理器.

为了方便理解, 这里打个比方. 如果把应用程序比喻成"智能机器人". 计算机世界就是这种机器人生活的世界, 编译器也可以理解为一种特殊的智能机器人, 它的工作就是生产其他各种机器人, 可以理解为一条生产线机器人. 计算机世界中每一个机器人, 只会说一种语言(CPU架构, 系统调用, 以及其他依赖库), 所以他们可以在某个说这种语言的国家(CPU+操作系统)工作.

比如说一条msvc生产线生产的机器人都只能说x86&windows语, 那么这些被生产出来的应用程序就只能在x86&windows上工作. 但生产线调整一下, 也可以用基于msvc的另一条生产线生产出能说AMD64&linux语的机器人. 当然由于msvc这条生产线作为智能机器人本身只能说都用x86&windows语, 所以这条生产线只能建在x86&windows这个国家. 通常我们把编译器所在的CPU和操作系统叫做host, 把做出来其他程序必须工作的CPU和操作系统叫做Target. 如果这两个一样就叫做本地编译(native compile), 如果不一样就叫做交叉编译(cross compile).

所以根据前面的批处理帮助, 我们可以知道我安装的当前版本的msvc支持8条生产线. 如果安装时选了linux支持, 则估计生产线要再乘以2了.

host target
x86 & windows x86 & windows
x86 & windows AMD64 & windows
x86 & windows ARM & windows
x86 & windows ARM64 & windows
AMD64 & windows x86 & windows
AMD64 & windows AMD64 & windows
AMD64 & windows ARM & windows
AMD64 & windows ARM64 & windows

顺便说一句, windows的批处理的语法跟POSIX(比如linux)的shell比, 弱爆了. 以前一直不喜欢使用, 但如果仔细读msvc的这一套批处理文件, 发现高手依然可以用其写出优雅的代码. 在windows下批处理胜在不需要额外安装别的支持(比如cygwin或者msys), 所以执行的启动速度也还比较快. 所以如果编译开发环境都是windows的话, 以后可以考虑改用windows的批处理解决小问题.

GCC

GCC是the GNU Compiler Collection的缩写, 是最流行的开源编译器, 不止支持C++, 还支持几乎所有编译型语言.

GCC原生于类unix系统(包括linux, 后面直接用linux代表所有类unix系统), 在windows上使用gcc通常有两个途径, cygwin或者MinGW. 这里说说他们的区别.

cygwin

cygwin是想要在windows上实现完整的POSIX接口, 使任何一个在linux上课执行的程序, 拿到windows只要基于cygwin重新编译一下就可以运行.

如果利用刚才的类比, cygwin中的gcc相当于给gcc配置了一个翻译机器人, 这样gcc每说出一个linux语, cgwin翻译机器人立马给翻译成windows语, 这样就可以在windows国进行生产了, 而且只要用这个生产线生产出来的程序也都需要配一个cygwin翻译. 这样就能保证所有的linux上的机器人可以原样设计不动, 在windows上生产一个一模一样的.

这个翻译机器人就是cygwin1.dll. 所以运行通过cygwin gcc编译出来的程序时, 一定要让正确版本的cygwin1.dll所在目录在系统PATH里. 从这里也可以看到, cygwin gcc不追求效率, 运行程序要比linux原生程序慢, 也比同样功能的windows原生程序慢.

cygwin安装按照官方指导来就行了.

MinGW

MinGW是Minimalist GNU for Windows的缩写, 通常读成"min gee double-you". MinGW是改造了GCC这个编译器以及其配套工具, 让其能够生产windows应用.

在那个类比里, 相当于把linux国的GCC生产线改造了一下, 让它本身说windows语, 这样就可以在windows上生产机器人, 并且生产出来的别的机器人也都说windows语了.

这样熟悉linux上gcc的用户就可以直接到windows上使用gcc了. 但是熟悉gcc的人一般也很难适应windows的shell环境, 所以又有人给MinGW提供了一个shell外壳, 叫msys, 这样常见的linux shell命令也可以在windows上执行.

刚才提到的这两个都有一个更新的可替代软件一个叫MingGW-w64, 一个叫msys2. 可以直接按照官方教程先安装msys2, 然后再利用pacman程序安装MinGW-w64.

1
pacman -S --needed mingw-w64-x86_64-toolchain

或者

1
pacman -S --needed mingw-w64-i686-toolchain

抄两句别人的话就是:

Cygwin = Lets put unix on windows, use as much native unix stuff as possible
MinGW = Lets keep unix out of windows as much as possible, us as much native windows stuff as possible.

clang

Clang读成"klang", 是基于LLVM的C家族语言编译器, LLVM是一个跟GCC差不多的开源编译框架. LLVM的产生是跟GCC暴露出来的问题紧密相关的. 比如GCC模块化做的不好, 无法给IDE提供足够的中间接口. 比如GCC的报错提示有时候完全不知所云. 比如gcc在windows上的效率很低. 还比如GCC是基于GPL协议. 对于GCC的这些缺点可以阅读这篇文章来详细了解. LLVM就是针对所有GCC的这些痛点对编译框架进行了重新设计, 所以按道理来讲应该更先进. 现在苹果是LLVM的主要赞助人. 但是在windows和linux世界由于路径依赖, Clang使用率应该是没有前面两个高.

网上有大量的文章对gcc vs clang vs msvc进行对比, 从比较编译效率, 到编译出来的程序的优化度, 到比较报错输出都有.

比如这篇Clang vs GCC vs MSVC: Diagnostics, 比较报错信息. 还有这篇VS produces code that is 1.5-2x slower than gcc and clang声称msvc编译时间比另外两个长. 比如这篇比较clang和gcc的编译后的执行效率. 这篇是中文的.

clang的安装完全按照官网来就可以了. 没什么坑好说的.

ToDo

下面这几个以后有时间都值得做一下. 1. 搞一个makefile, 可以对一个源代码同时使用三种编译器编译, 方便研究. 2. 对不太熟悉的msvc和clang, 研究一下编译链接选项. 3. 是否可以使用clang编译出可以在DEOS运行的程序. 4. 搞清楚LLVM是否可以跟VIM合作, 使VIM对C++代码的语义定位更加准确.