@TOC
正如我们享受他人的发明带来的巨大便利一样,我们也应该乐于有机会让自己的发明为他人服务,而且我们应该免费并慷慨地这么做。
——本杰明・富兰克林
给定源代码 hello.c
#include <stdio.h>
int main()
{
printf("Hello World");
return 0;
}
GCC 将 源代码变为可执行文件,分为 4 个阶段:预处理(预编译)、编译和优化、汇编和链接:
1.预处理(Preprocessing), 主要是对源文件中的宏定义等预处理指令进行预处理,生成
*.i文本文件;
2.编译(Compilation), 主要是对预处理完的*.i文件 进行分析和优化,生成对应的汇编代码*.s文件;
3.汇编(Assemble), 主要是将*.s文件 汇编为机器语言或指令,就是机器可以执行的二进制程序*.o文件
4.链接(Linking), 主要是链接程序运行所需的目标文件和依赖库文件,生成可执行文件*.exe文件(相对于Linux的.out),以二进制形式存储在磁盘中。
整个过程将高级语言翻译成了机器语言,而编译器就是这样的一个工具。GCC可以完成从预处理、编译、汇编、链接整个过程。但是平时使用Visual Studio等软件时并没有接触到这个过程,因为VS是集成开发环境(IDE、Integrated Development Environment),集成了代码编辑器,编译器,调试器和图像化用户界面,上述所有程序编译和链接过程都用一步build构建带过了。
Ⅰ.1 GNU 的 GCC/G++
GNU 计划,是由理查德·斯托曼在1983年9月27日公开发起的,目标是创建一套完全自由的操作系统。而 GNU 是一个类 Unix 操作系统。它是由多个应用程序、系统库、开发工具乃至游戏构成的程序集合。GNU 的开发始于 1984 年 1 月,称为 GNU 工程。GNU 的许多程序在 GNU 工程下发布;我们称之为 GNU 软件包。
GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言编译器,以 GPL 及 LGPL许可证所发行的自由软件,也是 GNU 工具链的主要组成部分之一。GNU编译器套件包括 C、C++、 Objective-C、 Fortran、Java、Ada和Go语言前端,也包括了这些语言的库(如libstdc++,libgcj等)。GCC的初衷是为 GNU 操作系统专门编写的一款编译器,支持的处理器架构: ARM、x86、x86-64、MIPS、PowerPC等,因此 GCC 通常是跨平台软件的编译器首选。
- gcc 是 GCC 中的 GUN C Compiler (C 编译器)
- g++ 是 GCC 中的 GUN C++ Compiler(C++编译器)
一个有趣的事实: 就本质而言,gcc 和 g++ 并不是编译器,也不是编译器的集合,它们只是一种驱动器,根据参数中要编译的文件的类型,调用对应的 GUN编译器。更准确的说法是:gcc 调用了 C compiler,而 g++ 调用了 C++ compiler。比如,用gcc编译一个c文件的话,会有以下几个步骤:
Step1:Call a preprocessor, like cpp.
Step2:Call an actual compiler, like cc or cc1.
Step3:Call an assembler, like as.
Step4:Call a linker, like ld
由于编译器是可以更换的,所以 gcc 不仅仅可以编译C文件,只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译。
可以这样理解,gcc 是 GCC 编译器的通用编译指令。根据程序文件的后缀名,gcc 指令可以自行判断出当前程序所用编程语言的类别,比如:
xxx.c:默认以编译 C 语言程序的方式编译此文件;
xxx.cpp:默认以编译 C++ 程序的方式编译此文件。
xxx.m:默认以编译 Objective-C 程序的方式编译此文件;
xxx.go:默认以编译 Go 语言程序的方式编译此文件;
Ⅰ.1.1 gcc 和 g++ 的区别
标题 gcc g++ 是 GUN C Compiler GUN C++ Compiler 对于 *.c和*.cpp文件gcc 分别当做 c 和 cpp 文件编译(c和cpp的语法强度是不一样的) g++ 则统一当做 cpp 文件编译 链接 gcc 不会自动链接 STL;在编译 C 文件时,可使用的预定义宏是比较少的 g++ 会自动链接标准库 STL 此外:
- gcc 在编译 cpp 文件时 / g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏,这些宏如下:
#define __GXX_WEAK__ 1 #define __cplusplus 1 #define __DEPRECATED 1 #define __GNUG__ 4 #define __EXCEPTIONS 1 #define __private_extern__ extern
- 在用 gcc 编译 c++ 文件时,为了能够使用 STL,需要加参数
–lstdc++,但这并不代表 gcc –lstdc++ 和 g++等价,它们的区别不仅仅是这个。
Ⅰ.1.2 gcc 和 g++ 的安装
安装软件 必须要有管理员权限
# ubuntu $ sudo apt update # 更新本地的软件下载列表,得到最新的下载地址 sudo apt install gcc g++ # 通过下载列表中提供的地址下载安装包,并安装 # centos $ sudo yum update # 更新本地的软件下载列表,得到最新的下载地址 $ sudo yum install gcc g++ # 通过下载列表中提供的地址下载安装包,并安装
Ⅰ.1.3 gcc 和 g++ 的使用
参数
-g - turn on debugging (so GDB gives morefriendly output) -Wall - turns on most warnings -O or -O2 - turn on optimizations -o - name of the output file -c - output an object file (.o) -D - specify a macro definition -E - just Preprocess the specified file -S - just Compile the specified file -c - just Assembling the specified file -I - specify an includedirectory -L - specify a libdirectory -l - link with librarylib.a示例:
g++ -ohelloworld -I/homes/me/randomplace/include helloworld.c
所以,在 Linux 甚至 Windows 上各种涉及开发环境配置,源码编译的地方,都离不开 gcc 和 g++。
Ⅰ.2 LLVM 的 clang/clang++
LLVM 项目是一个模块化、可重用的编译器和工具链技术的集合。LLVM 最初是伊利诺伊大学的一个研究项目,目的是提供一种现代的、基于SSA的编译策略,能够支持任意编程语言的静态和动态编译。从那时起,LLVM 已经发展成为一个由多个子项目组成的伞式项目,其中许多子项目被各种商业和开源项目用于生产,并被广泛用于学术研究。LLVM 项目中的代码是根据“Apache 2.0许可证(LLVM例外)”获得许可的。LLVM的主要子项目有:
- LLVM核心库 提供了一个现代的独立于源和目标的优化器,以及对许多流行 CPU(以及一些不太常见的CPU)的代码生成支持。这些库是围绕一个指定良好的代码表示构建的,称为LLVM中间表示(“LLVM IR”)。LLVM核心库有很好的文档记录,并且特别容易发明自己的语言(或移植现有的编译器)来使用LLVM作为优化器和代码生成器。
- Clang 是一个“LLVM原生”的
C/C++/Objective-C编译器,旨在提供惊人的快速编译、极其有用的错误和警告消息,并为构建优秀的源代码级工具提供平台。Clang静态分析器和Clang整洁是自动查找代码中错误的工具,也是可以使用Clang前端作为库来解析 C/C++ 代码的工具的很好的例子。 - LLDB项目 建立在LLVM和Clang提供的库之上,以提供一个出色的本地调试器。它使用 Clang AST 和表达式解析器、LLVM JIT、LLVM反汇编程序等,因此它提供了“刚好有效”的体验。它在加载符号方面也比GDB快得多,内存效率也高得多。
- libc++和libc++ABI项目 提供了一个符合标准的 C++ 标准库的高性能实现,包括对 C++11 和 C++14 的完全支持。
- 编译器rt项目 提供了低级别代码生成器支持例程(如
__fixunsdfdi)的高度调优实现,以及当目标没有短序列的本机指令来实现核心 IR 操作时生成的其他调用。它还为动态测试工具(如AddressSanitizer、ThreadManitizer、MemoryManitizer和DataFlowSanitizer)提供了运行时库的实现。 - MLIR子项目 是一种构建可重用和可扩展编译器基础设施的新方法。MLIR 旨在解决软件碎片化问题,改进异构硬件的编译,显著降低构建特定领域编译器的成本,并帮助将现有编译器连接在一起。
- OpenMP子项目 提供了一个 OpenMP 运行时,用于 Clang 中的 OpenMP 实现。
- polly项目 实现了一套缓存位置优化,以及使用多面体模型的自动并行和矢量化。
- libclc项目 旨在实现 OpenCL 标准库。
- klee项目 实现了一个“symbolic virtual machine”,它使用theorem prover来尝试通过程序评估所有动态路径,以找出错误并证明函数的属性。klee 的一个主要功能是,它可以在检测到错误的情况下生成测试用例。
- LLD项目 是一个新的链接器。这是系统链接器的替代品,运行速度更快。
- BOLT项目 是一个链接后优化器。它通过基于采样探查器收集的执行概要文件优化应用程序的代码布局来实现改进。
除了LLVM的官方子项目外,还有许多其他项目将 LLVM 的组件用于各种任务。通过这些外部项目,可以使用LLVM 编译 Ruby、Python、Haskell、Rust、D、PHP、Pure、Lua、Julia 和许多其他语言。LLVM 的一个主要优势是它的多功能性、灵活性和可重用性,这就是为什么它被用于各种不同的任务:从对 Lua 等嵌入式语言进行轻量级 JIT 编译到为大型超级计算机编译 Fortran 代码,无所不包。
因此,clang 作为 LLVM 的一个子项目,是一个 C、C++、Objective-C 等语言的轻量级编译器,遵循BSD协议。Clang所具有的 编译速度快、内存占用小、兼容GCC 等优点使得很多工具都在使用它。
Ⅰ.3 GCC 与 Clang 的区别
Clang本身性能优异,其生成的AST所耗用掉的内存仅仅是GCC的20%左右。Clang性能测试证明Clang编译Objective-C代码时速度为GCC的3倍,还能针对用户发生的编译错误准确地给出建议。2014年1月发行的FreeBSD10.0版将Clang/LLVM作为默认编译器。
标题 GCC Clang license BSD GPLv3 特性 除支持 C/C++/ Objective-C/Objective-C++语言外,还是支持Java/Ada/Fortran/Go等;当前的 Clang 的 C++ 支持落后于 GCC;支持更多平台;更流行,广泛使用,支持完备。编译速度快;内存占用小;兼容 GCC;设计清晰简单、容易理解,易于扩展增强;基于库的模块化设计,易于IDE集成;出错提示更友好。 此外,二者使用的宏不同:
GCC 定义的宏包括:__GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__ __GNUG__Clang 除了支持 GCC 定义的宏之外还定义了:
__clang__ __clang_major__ __clang_minor__ __clang_patchlevel__
Ⅱ.1 Windows平台下的 Mingw / MSVC
-
MinGW (Minimalist GNU for Windows)是一个可自由使用和自由发布的 Windows 特定头文件和使用 GNU 工具集导入库的集合,允许在 Windows 平台生成本地的 Windows 程序而不需要第三方 C 运行时(C Runtime)库。
运行时库:支持程序运行的基本函数的集合,一般是静态库 lib 或动态库 dll。 -
MSVC,Microsoft Visual C/C++ Compiler) 就是微软(MS)的VC运行库,由微软开发的 VC运行时库,被 Visual Studio IDE 所集成,因此使用VS时会附带MSVC编译器。Windows 很多程序在编制的时候使用了微软的运行库,这将大大减少了软件的编码量,提高了兼容性。
因此,MinGW 和 MSVC都是 Windows C/C++ 语言编译支持,配置环境时遇到两者择其一即可。
MinGW 各版本参数 (Version、Architecture、Threads、Exception) 说明
- Version:指的是 GCC 编译器的版本,我选择的是当前最新版本 8.1.0,一般建议选择最新的版本;
- Architecture:指的是电脑的系统类型,i686 表示的是 构建32位应用程序,x86_64 表示的是 构建64位应用程序;
- Threads:指的是线程模型,posix 或 win32
- POSIX(Portable Operating System Interface,可移植操作系统接口),是 UNIX 系统的一个 API 设计标准,很多类 UNIX 系统也在支持兼容这个标准,如 Linux 操作系统。如果在 Windows 下开发 Linux 应用程序,则选择 posix;
- Win32,是 Windows 系统下一个 API 设计标准,如果开发 Windows 平台下的应用程序,就需要选择 Win32;
- Exception:指的是异常处理模型。i686 系统架构有两种选择:dwarf 和 sjlj;x86_64 系统架构也有两种选择:seh 和 sjlj。
sjlj,seh,dwarf 三者的区别:
在C++中有try…throw…catch,当执行这种结构时,需要保存现场还原现场,而 sjlj,seh,dwarf 正是实现这类过程的三种方式。
sjlj 全称是 SetJump / LongJump,前者设还原点,后者跳到还原点。可用于 32 位或者 64 位系统。
seh(Structured Exception Handling,结构化异常处理)是 Borland 公司的,微软买了其专利使用权,它利用了 FS 段寄存器,将还原点压入栈,收到异常时再弹出。相较而言,sjlj 是 C 标准库就有的东西,seh 在 2014 年前是有专利的,从性能上说 seh 比 sjlj 快。只用于64位系统。
dwarf 只支持32位系统 – 没有永久的运行时间开销 – 需要整个调用堆栈被启用,这意味着exception不能被抛出,例如Windows系统DLL。
综上所述:
【x86_64 64位】
1、seh 是新发明的,性能比较好,但不支持 32位。
2、而 sjlj 则是古老的。只用于64位系统。
3、sjlj 稳定性好,支持 32位和64位。
因此,x86_64 系统架构的推荐使用 seh 的异常处理模型。
【i686 32位】
1、dwarf 只支持 32 位,但是 dwarf 的性能要优于 sjlj。
2、sjlj 支持 32 位或64 位,
因此,i686 系统架构的推荐使用 dwarf 的异常处理模型。
Ⅱ.2 Make/CMake
有了编译器 gcc、clang 等等, 为什么要有make这个构建生成器?
编译 hello.c 非常简单,只需要 gcc hello.c就可以了。但当项目庞大起来后,每次都要用gcc命令逐个去编译时,就很容易混乱而且工作量大。
所以,GNU 发明了 make 这个工具软件,可以编写 makefile 文件来指定特定的项目构建过程,当项目一个文件的代码更改时,只需要重新 make 一下就可以了。但 make 依然有很多不足,比如
- make 对于类 unix 系统是通用的,但对 windows 系统并不友好(不能跨平台);
- make 语法简单,也就导致了它功能的限制;
- 不同编译器的语法规则不同,编写的 makefile 语法如果适合 GCC 则不适合 MSVC。
所以,CMake应运而生:CMake 是比 Make 更高一层的构建生成器,Make是编写对应编译器的 makefile 从而调用编译器实现编译,而 CMake 是写一份独立的 CmakeList.txt 文件,然后该文件会根据当前系统环境选择适合的构建生成器(如 VS 或者 make),然后将 CmakeList.txt 翻译为适合的文件,再进一步调用编译器进行项目构建。如下图所示:
- Q1. 如何通过 mingw、cmake 对源代码进行编译呢?
- Q2. ...
A2: ...
文章撰写不易,转载请标明出处!
如果感觉本文对您有帮助,请留下您的赞,您的支持是我坚持写作分享的最大动力,谢谢!
References
1. MinGW-w64 - for 32 and 64 bit Windows
2. Download | CMake
3. Windows下的 MinGW 和 MSVC 的区别
4. gcc/g++/MingW/MSVC 与 make/CMake 的关系
5. mingw 不同版本
6. GCC 的 gcc 和 g++
7. gcc g++ make cmake含义与区别
8.编译器Clang
可以肯定的是学海无涯,这篇文章也会随着对 gcc、mingw、cmake 的深入学习而持续更新,
欢迎各位在评论区留言进行探讨交流。