GCC介绍

编译过程

编译系统(compilation system):预处理器(pre-processor)、编译器(compiler)、汇编器(assembler)、链接器(linker)
编译系统
预处理阶段:处理字符#开头的命令,即:1)将头文件的内容插入程序文本中。2)宏定义替换。3)条件编译(#if #ifdef),不被编译的部分变为空行。4)删除注释

编译阶段:通过编译器将源程序翻译成汇编程序(assembly-language program)

汇编阶段:将汇编程序翻译成机器语言指令,并将其打包成可重定位目标程序(relocatable object program)

链接阶段:链接器将各个.o文件合并成可执行文件。链接器使得分离编译成为可能。在编写大型程序时,可将模块分小,由此达到独立修改和编译不同模块的目的:未被修改的模块不用重新编译,而只需将修改后的模块编译,重新链接即可。e.g. hello.c中的printf函数存在printf.o(已经单独预编译了的目标文件)中,链接器将其合并后得到可执行文件

GCC介绍

  • GCC:GNU Compiler Collection(GNU 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C等语言。

  • gcc是GCC中的GUN C Compiler(C 编译器)

  • g++是GCC中的GUN C++ Compiler(C++编译器)

早期 GCC 的全拼为 GNU C Compiler,即 GUN 计划诞生的 C 语言编译器,显然最初 GCC 的定位确实只用于编译 C 语言。但经过这些年不断的迭代,GCC 的功能得到了很大的扩展,它不仅可以用来编译 C 语言程序,还可以处理 C++、Go、Objective -C 等多种编译语言编写的程序。与此同时,由于之前的 GNU C Compiler 已经无法完美诠释 GCC 的含义,所以其英文全称被重新定义为 GNU Compiler Collection,即 GNU 编译器套件

在已编辑好 C 语言或者 C++ 代码的前提下,如何才能调用 GCC 编译器为我们编译程序呢?很简单,GCC 编译器已经为我们提供了调用它的接口,对于 C 语言或者 C++ 程序,可以通过执行 gcc 或者 g++ 指令来调用 GCC 编译器

无论是 gcc 还是 g++, 他们的定位都是 driver.

driver 负责调用编译器(狭义), 把源码编译到汇编代码. 比如 C 语言的编译器(狭义)是 cc1, 而 C++ 语言的编译器(狭义)是 cc1plus。driver 再调用 as, 把汇编代码变成二进制代码. 最后调用 ld, 负责把二进制代码拼在一起.

gcc 和 g++ 的区别无非就是调用的编译器不同, 并且传递给链接器的参数不同.

具体而言:

g++:

  • g++ 会把 .c 文件当做是 C++ 语言 (在 .c 文件前后分别加上 -xc++-xnone, 强行变成 C++), 从而调用 cc1plus 进行编译.

  • g++ 遇到 .cpp 文件也会当做是 C++, 调用 cc1plus 进行编译.

  • g++ 还会默认告诉链接器, 让它链接上 C++ 标准库.

gcc:

  • gcc 会把 .c 文件当做是 C 语言. 从而调用 cc1 进行编译.

  • gcc 遇到 .cpp 文件, 会处理成 C++ 语言. 调用 cc1plus 进行编译.

  • gcc 默认不会链接上 C++ 标准库.

只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译。可以这样理解,gcc 是 GCC 编译器的通用编译指令,因为根据程序文件的后缀名,gcc 指令可以自行判断出当前程序所用编程语言的类别

但如果使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件。也就是说,对于 .c 文件来说,gcc 指令以 C 语言代码对待,而 g++ 指令会以 C++ 代码对待。但对于 .cpp 文件来说,gcc 和 g++ 都会以 C++ 代码的方式编译。

值得注意的是,Apple公司曾经一直使用GCC作为官方的编译器。 但是由于GCC开发社区对Apple所提的需求,给予的优先级始终不高,甚至很多Apple的重要需求基本不做考虑。 于是,财大气粗的Apple一怒之下,决定放弃GCC,基于LLVM重新开发了编译工具Clang,支持 C、C++、Objective-C等语言。 因此,目前macOS上自带的默认gcc命令,实际上调用的是clang。 希望在macOS上使用GCC,需要自行安装

gcc所遵循的部分约定规则

.c为后缀的文件,C语言源代码文件;

.a为后缀的文件,是由目标文件构成的档案库文件;
.C或.cc或.cxx为后缀的文件,是C++源代码文件;

.h为后缀的文件,是程序所包含的头文件;

.i为后缀的文件,是已经预处理过的C源代码文件;

.ii为后缀的文件,是已经预处理过的C++源代码文件;

.m为后缀的文件,是Objective-C源代码文件;

.o为后缀的文件,是编译后的目标文件;

.s为后缀的文件,是汇编语言源代码文件;

.S为后缀的文件,是经过预编译的汇编语言源代码文件。

gcc编译优化参数

gcc提供了4级优化参数,分别是-O0-O1-O2-O3。 一般来说,数字越大,所包含的编译优化策略就越多。

  • -O0参数表示不使用任何优化策略,是gcc默认的优化参数。 因为没有使用任何优化策略,编译得到的机器码与程序源码高度对应,两者之间基本可以建立一一对应的关系。所以,-O0优化非常适合用于程序调试,并且通常和生成调试信息的参数-ggenerate debug information)配合使用。-g参数会在编译时给生成的二进制文件附加一些用于代码调试的信息,比如符号表和程序源码。
  • -O1会尽量采用一些不影响编译速度的优化策略,降低生成的二进制文件的大小,以及提高程序执行的速度。
  • -O2使用-O1中的所有优化策划,还会采用一些会降低编译速度的优化策略,以提高程序的执行速度。

gcc编译流程

此部分来源于这篇文章:https://zhuanlan.zhihu.com/p/342151242

使用gcc编译C/C++程序时,主要的编译流程如下,包含预处理编译汇编链接等四个步骤。 以输入C语言程序源码文件b.c为例,直接调用命令gcc b.c,将会完整执行以下流程,并生成对应的可执行二进制文件a.out。 注意,这里gcc默认输出就是固定的a.out(通过使用参数-o(output),可以指定输出文件的名称。 例如gcc b.c -o b.bin,将生成可执行文件b.bin,而不是默认的a.out)。 在GCC工具链中,汇编由工具as完成,链接则由工具ld完成。

      -E          -S          -c          
b.c ------> b.i ------> b.s ------> b.o ------> a.out
      gcc         gcc         as          ld

gcc使用以下指令,将会使其编译流程停止在对应位置:

  • -E,(prEprocessing),执行到预处理步骤之后,即处理C/C++源码中#开头的指令,包括宏展开以及#include头文件引入等等。 该指令默认不输出文件,可以使用-o指令输出约定后缀为*.i的文件。
  • -S,(aSsembly),(?这一步是将预处理后的代码编译成汇编代码,为什么不叫compilation?)执行到编译步骤之后,生成汇编文件,但不生成二进制机器码。 该指令默认的输出文件后缀为*.s
  • -c,(compilation),(?这一步为什么不叫assembly?)执行到汇编步骤之后,调用工具as,从汇编码生成二进制机器码,但不进行链接。 该指令默认的输出文件后缀为*.oobject)。
  • 不带以上参数调用gcc将会完整执行以上流程,即执行到到链接(linking)步骤之后。 链接步骤实际上调用链接工具ld来执行,会将源码生成的二进制文件,库文件,以及程序的启动部分进行组合,从而形成一个完整的二进制可执行文件。

一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件

Makefile

makefile关系到了整个工程的编译规则,一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,我们不可能用gcc命令对每所有文件一个个进行编译,这就必须要编写一个名为Makefile的文件来告诉make如何编译和链接程序,所以Makefile就相当于是一个工程文件的编译规则,它描述了工程中文件之间的关系并提供用于更新每个文件的命令。它规定了工程中哪些文件需要编译,哪些文件不需要编译,哪些文件需要先编译,哪些文件需要后编译,那些文件需要重建等等。编译整个工程需要涉及到的,在 Makefile 中都可以进行描述。换句话说Makefile 可以使得我们的项目工程的编译变得自动化,不需要每次都手动输入一堆源文件和参数。

make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具

在工程中,可执行文件是从目标文件更新的,而目标文件又是通过编译源文件制成的。一旦存在合适的Makefile,则每次更改一些源文件时,都会使用make来执行所有必要的重新编译。

hello.c所在目录下新建一个文件Makefile, 输入以下内容并保存:

hello:hello.c
    gcc hello.c -o hello    # 注意开头的tab, 而不是空格

.PHONY: clean

clean:
    rm hello    # 注意开头的tab, 而不是空格

返回命令行, 键入make, 你会发现make程序调用了gcc进行编译. Makefile文件由若干规则组成, 规则的格式一般如下:

目标文件名:依赖文件列表
    用于生成目标文件的命令序列   # 注意开头的tab, 而不是空格

我们来解释一下上文中的hello规则. 这条规则告诉make程序, 需要生成的目标文件是hello, 它依赖于文件hello.c, 通过执行命令gcc hello.c -o hello来生成hello文件.

如果你连续多次执行make, 你会得到"文件已经是最新版本"的提示信息, 这是make程序智能管理的功能. 如果目标文件已经存在, 并且它比所有依赖文件都要"新", 用于生成目标的命令就不会被执行.

上面例子中的clean规则比较特殊, 它并不是用来生成一个名为clean的文件, 而是用于清除编译结果, 并且它不依赖于其它任何文件

make相关命令:

  • make:根据Makefile文件编译源代码、连接、生成目标文件、可执行文件。
  • make clean:清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件。(在make的时候,会重新生成objects, 也就说新的object覆盖旧的objects。而make clean 是删除旧的objects。所以应该是make已经含有了make clean的功能。但是实际上有时直接make,上次留下来的object不能清干净。所以多次编译调试运行, 有时候必须make clean,再使用make
  • make install:将编译成功的可执行文件安装到系统目录中,一般为/usr/local/bin目录。
  • make dist:产生发布软件包文件(即distribution package)。这个命令将会将可执行文件及相关文件打包成一个tar.gz压缩的文件用来作为发布软件的软件包。它会在当前目录下生成一个名字类似“PACKAGE-VERSION.tar.gz”的文件。PACKAGE和VERSION,是我们在configure.in中定义的AM_INIT_AUTOMAKE(PACKAGE, VERSION)。

CMake

当工程非常大的时候,手写makefile非常麻烦,如果换了个平台makefile还需要重新修改。这时候就出现了Cmake这个工具。cmake是跨平台项目管理工具,它用更抽象的语法来组织项目,**可以根据CMakeLists.txt文件去生成makefile(最重要的是可以跨平台生成对应平台能用的makefile,不用再自己去修改了)**给make使用。

CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后按照Makefile文件的规则,用make命令编译源码生成可执行程序或共享库(so(shared object))。因此CMake的编译基本就两个步骤:

  1. cmake
  2. make

cmake 指向CMakeLists.txt所在的目录,例如cmake .. 表示CMakeLists.txt在当前目录的上一级目录。cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译

mkdir build
cd build
cmake ..
make
设置构建类型

构建类型(Build Type)指的是编译构建一个代码工程时采用的配置。对于使用IDE的工程,一般可以在IDE内的选项上修改构建类型,例如Visual Studio工程中的Configuration,默认分为Debug和Release,在每次编译前可以选择使用哪一种。但对于命令行式的构建系统,一般需要自己调整makefile中的编译选项,来实现不同的构建类型。使用cmake可以不必手动修改编译选项,能够方便的切换构建类型。

CMake预先内置了四种构建类型:Debug,Release,RelWithDebInfo,MinSizeRel,可以满足大部分的使用情况,并通过预置的变量CMAKE_BUILD_TYPE表示当前的构建类型,可以通过修改它的值来改变构建类型,变量的初始值为空,表示不指定任何构建类型。

可以通过两种方式指定生成的Makefile的编译模式,一种是在cmake命令后显示指定编译模式,一种可以把编译的模式配置写在CMakeLists.txt

  • 在cmake命令后加上 -DCMAKE_BUILD_TYPE=选项显式指定编译模式

    • 在build目录下使用 cmake .. -DCMAKE_BUILD_TYPE=Debug,在gdb中有更好的调试信息,方便调试。
    • 使用cmake .. -DCMAKE_BUILD_TYPE=Release编译不带调试信息,就是Release版。

    一般Debug和Release必须在不同的目录下编译,否则每次当切换模式时必须把编译文件全部删掉。也可以新建两个目录Debug和Release来分别用于构建相应的模式:

    mkdir Release  
    cd Release  
    cmake -DCMAKE_BUILD_TYPE=Release ..  
    make  
    

    mkdir Debug  
    cd Debug  
    cmake -DCMAKE_BUILD_TYPE=Debug ..  
    make  
    
  • 也可以在CMakeLists.txt里手动指定CMAKE_BUILD_TYPE

    SET(CMAKE_BUILD_TYPE "Debug”)
    or
    SET(CMAKE_BUILD_TYPE "Release")
    

cmake切换模式时必须将之前的编译文件删除,使用make clean将之前make命令生成的.o文件或可执行文件删除。再使用make命令重新编译。(不知道cmake是否会将之前的Makefile覆盖,最好使用rm -rf build命令删除之前的Makefile文件,再使用cmake切换模式,重新生成Makefile文件,再使用make命令重新编译

综上,它们的关系如下:

GCC是C/C++语言的编译工具,Make是增量式(编译)批处理工具,CMake是Make脚本生成工具。 在现代C/C++项目的构建中,它们的关系如下。

              cmake           make       gcc
CMakelist.txt -----> Makefile ----> Cmds ---> Binary
  1. 开发者需要编写CMakelist.txt文件,来配置项目相关的CMake参数。
  2. 通过运行cmake命令,自动生成对应平台的Make工具自动构建脚本Makefile文件。 当然,CMake也支持生成其他的构建工具的配置文件,比如Xcode的xxxx.xcodeproj,Visual Studio的xxxx.sln,Ninja的xxxx.ninja等等。 目前,大多数开源的C/C++项目都支持使用CMake生成Makefile文件。
  3. 再调用make命令,使用Make工具进行自动构建。 Makefile文件可以看成是一系列依赖于文件的Shell命令。 它基于文件修改的时间戳来实现增量式处理。 使用Make工具来编译C/C++项目时,一般会使用Shell命令来调用gcc自动化增量式地实现C/C++源代码的编译链接等一系列工作。
  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值