GCC 编译简明教程

前言

GCC,全称The GNU Compiler Collection,包含了 C, C++, Objective-C, Fortran, Ada, Go等语言的编译器前端以及这些语言所依赖的一些库文件。虽然现在的IDE基本可以搞定很多编译的事情,但是很多时候,b比如我们看源码的时候、自己编写一些Python C++扩展模块的时候等,使用IDE可能会比较繁琐,这时候就需要手动编译,需要看懂或者会编写编译脚本,这样,我们想在别人的基础上作些改动才有可能。因此有必要了解一下GCC。
需要说明的是,这里的GCC和你在命令行调用的gcc并不是一个东西。做个简单但很可能不完全正确的说明,GCC表示一套编译器的集合,而gcc,最开始表示GUN C Compiler,用于编译C,后来随着越来越多东西的加入,变成了GCC。现在gcc和g++只不过是GNU Compiler Collection的驱动程序。他们之间最大的区别可能就是他们链接的时候所使用的链接库不同。简单点记就是用gcc编译C,g++编译C++,当然,你也可以-x选项去使用gcc编译其他语言,例如使用gcc -xc++去编译C++。

参数类型分类

  1. 控制输出结果,如-c -S -E -o file -x language等;
  2. 控制当前编译源码所需遵循的语言标准,如-ansi -std=standard -fgnu89-inline等;
  3. 控制警告信息,如-Wall等;
  4. 控制调试,如-g -glevel*等;
  5. 控制优化,如-O -O0 -O1 -O2 -O3 -Os -Ofast -Og等;
  6. 控制预处理;
  7. 控制汇编;
  8. 控制链接,如-shared -shared-libgcc -symbolic -llibrary
  9. 控制代码生成;
  10. 控制编译过程所需要搜索的目录,如-Bprefix -Idir -I-
  11. 硬件依赖;
  12. 开发者相关。

这里只是分类简单介绍一些常用的选项。更多更详细的信息参阅参考文档的内容。

控制输出结果

我们知道,编译过程可以分为预处理、编译、汇编、链接四个阶段。这四个阶段按顺序进行,并且每个阶段都会产出用于下一个阶段的中间产物。由于编译并不是一步到位,因此我们可以通过参数告诉编译进行到哪一步,例如,我们只想编译得到目标文件,而不想直接链接得到可执行文件,那么我们可以在命令行中使用-c参数来告诉编译器不进行链接操作。
控制编译输出的结果主要通过下面四个参数:

  • -E:只进行预处理
  • -S:只进行到编译阶段,不进行汇编和链接操作
  • -c:不进行链接操作,只生成目标文件
  • -o:指定输出文件

控制源码所遵循的标准

每一种语言都有自己独特的语法语义,随着语言的不断发展,每种语言的语法也在变化着。比如C++,相关的标准有C++98,C++11等,由于GCC编译器可以接受多个标准,并且有其默认使用标准,使用不同的标准去编译一份源码可能得到的结果不尽相同,所以有很多时候,我们需要指定当前源码所遵循的标准。例如,我们需要使用C++11标准,我们可以通过-std=c++11来指定。

控制警告信息

有个笑话,说有个人走到悬崖边看到一个牌子写着:

warning: 前面是悬崖,不要再往前走了。

可是这个人还是摔死了,因为他是程序员,总是习惯性的忽略所有warning。

在编译过程中,很可能会出现一些警告信息,虽然有时候这些警告并不影响结果,但是警告的出现通常预示着代码有可能存在风险或者潜藏着错误。因此我们可以选择让编译器报告或者步报告某些特定警告信息。一般指定显示特定警告信息的参数都以-W开头,例如,当需要警告使用了隐式声明时,可以用-Wimplicit;与之相对,当希望编译器忽略某一类型的警告信息时,一般用-Wno开头的参数通知编译器,例如使用-Wno-implicit会关掉所有使用了隐式声明的警告。
有些参数,本身并不是控制显示特定警告信息,而是控制其他参数,例如-Wall,这并不是“墙壁”的意思,而是一个有多个控制警告信息的参数的集合,使用了-Wall就相当于同时使用了多个其他-W开头的参数,例如使用-Wall就相当于同时使用了-Waddress -Warray-bounds=1 -Wbool-compare以及上面提到的-Wimplicit等许多参数。

控制调试

当需要调试的时候,一般需要告诉编译器去生成一些额外的信息用于调试,很多时候,只需要指定-g就够了。
当然,用于控制调试的参数远不止于此。与控制警告信息的参数类似,控制调试的参数很多都是以-g开头,例如,当需要指定所产生的调试信息的格式时,可以使用-gdwarf、-gstabs、-gxcoff等,它们分别表示调试信息的格式为DWARF、stabs、XCOFF。

控制优化

当不指定优化策略的时候,编译器的目标将是最小化编译过程中的开销,这个开销一般只时间。
但是很多时候,我们需要在程序运行效率或者程序体积方面作出优化。当然,有得必有失,优化之后我们也会失去程序的可调试性或者在时间上多花时间。我们可以使用-O开头的参数告诉编译器我们想要优化的内容。

  • -O0:基本不做优化;
  • -O\-O1:尝试优化程序体积和执行时间;
  • -O2:比-O1作出更进一步的优化;
  • -O3:比-O3更进一步;
  • -Os:优化程序体积
  • -Og:优化调试体验

控制链接

编译的最后阶段便是链接。就相当于制作一台电脑的零部件都已经正确加工完毕摆放在那,但是距离这台电脑能跑起来还差正确组装。链接就是负责把所有目标文件组装成一个可执行文件或者一个库文件。
在链接阶段,有很多参数可以控制链接过程,比如,使用什么链接器去做链接、传递什么参数给选定的链接器、链接生成物的作用、程序的入口在那儿、链接此程序还需要那些库辅助等。
链接阶段主要用到的参数有以下几个:

  • -fuse-ld:选择使用的链接器,可选项有-fuse-ld=bfd -fuse-ld=gold -fuse-ld=lld分别表示使用bfd链接器、gold链接器、LLVM lld链接器;
  • -shared\-static:告诉编译器链接生成物是一个库,而不是可执行程序。其中-shared表示生成的是动态链接库,例如Linux中的.so库。由于不是所有系统都支持动态链接库,因此,如果使用了-shared参数,必须同时提供-fpic 或 -fPIC参数;
  • -l library\-llibrary:提供链接过程中所依赖的其同库文件。需要注意的是-l library这种用法,只有在Linux这类系统中有效。另外,库文件搜索所使用的目录包括一些标准目录以及-L参数所指定的目录;
  • -e entry\-entry=entry:指定程序入口;
  • -Wl,option:指定传递给选择的链接器的参数,如果option包含多个参数,用逗号作为分隔符,例如-Wl,-Map, output.map表示 把 -Map=output.map传递给了链接器;

控制搜索目录

编译过程中,有时候仅仅是标准目录是不够的,很多时候需要的一些头文件、三方库、编译器的部分依赖等并不在标准目录中,所以需要告诉编译器,去那里可以找到所需要的这些文件。
下面介绍一些常见的参数:

  • -Bprefix:指定编译器自身所需要的可知性文件、数据文件、包含文件等;
  • -Ldir:指定链接阶段使用-l指定的库文件的搜索目录;
  • -I dir:指定头文件搜索目录;
  • -I-\-iquote:指定引用类型的头文件(形如#include "file.h")的搜索路径;
  • -isystem\-idirafter:添加的目录类似于标准目录。
    既然-I\-iquote\-isystem\-idirafter等都是指定头文件搜索路径,那么编译器先搜索那里再到那里呢?其搜索顺序如下:
  1. 当前目录;
  2. 搜索-iquote指定的目录;
  3. 搜索-I指定的目录;
  4. 搜索-isystem指定的目录;
  5. 搜索标准目录;
  6. 搜索-idirafter指定的目录。
    确定了搜索顺序,我们就能做一些选择,例如有和标准目录中的头文件同名的头文件,我们可以选择放在标准目录前或后,这样就能选择是否覆盖标准目录的头文件。需要说明的是,第一、第二步只适用于引用类型头文件。

高级功能

类似于一些硬件相关、开发者相关的参数,这里就不做介绍了,有兴趣的可以参阅这里。

举个栗子

上面简单介绍了一些GCC的命令,当我们需要编译的时候,只需要分别在这几类命令中选出一些我们需要的,就可以编译了。 比如,我写了个Python的C++扩展模块,我现在需要编译:

  1. 首先,确定使用g++作为驱动;

  2. 第二部,我想我的程序体积和运行时间都比较好,所以我选择-O3优化;

  3. 第三步,关于警告信息,我就使用-Wall去打开常用的开关;

  4. 第四步,这是个动态链接库,所以我指定-shared以及-fPIC;

  5. 第五步,通过-o参数自己定一个库文件名;

  6. 第六步,通过-I来指定下编译过程中需要的一些头文件所在的目录;

  7. 其他的,类似预处理、调试及其他相关的参数,我这里并没有特殊要求,所以就不使用了。 那么我的编译命令就是下面这样子:

g++ -O3 -Wall -shared -std=c++11 -I/home/example/playground/pybind11/include my_module.c -o example.so -I/usr/include/python3.5m -I//home/example/playground/pybind11/include -fPIC

总结

这里只是个人理解的一个简单介绍。面对看似复杂的事情,如果能找到方法简化,定能事半功倍。

References

https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/index.html#SEC_Contents



这就是我的底线!!欢迎扫码或者微信搜索TensorBoy并关注获取更多最新文章 , 学习使我快乐!

你可能感兴趣的:(GCC 编译简明教程)