GCC编译器
GCC(GUN Compiler Collection,GNU编译器套件)是由GNU开发的编程语言译器。GNU编译器套件包括C、C++、Objective-C、 Fortran、Java、Ada和Go语言前端,也包括了这些语言的库(如libstdc++,libgcj等)。
GCC的组成部分
GCC 是由许多组件组成的。表 1 列出了 GCC 的各个部分,但它们也并不总是出现 的。有些部分是和语言相关的,所以如果没有安装某种特定语言,系统:中就不会出现相关的文件。
部分 | 描述 |
---|---|
c++ | gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 g++ 一样 |
ccl | 实际的C编译程序 |
cclplus | 实际的 C++ 编译程序 |
collect2 | 在不使用 GNU 连接程序的系统上,有必要运行 collect2 来产生特定的全局初始化代码(例如 C++ 的构造函数和析构函数) |
configure | GCC 源代码树根目录中的一个脚木。用于设置配置值和创建GCC 编译程序必需的 make 程序的描述文件 |
crt0.o | 这个初始化和结束代码是为每个系统定制的,而且也被编译进该文件,该文件然后会被连接到每个可执行文件中来执行必要的启动和终止程序 |
cygwin1.dll | Windows 的共享库提供的 API,模拟 UNIX 系统调用 |
f77 | 该驱动程序可用于编译 Fortran |
f771 | 实际的 Fortran 编译程序 |
g++ | gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 c++ 一样 |
gcc | 该驱动程序等同于执行编译程序和连接程序以产生需要的输出 |
gcj | 该驱动程序用于编译 Java |
gnat1 | 实际的 Ada 编译程序 |
gnatbind | 一种工具,用于执行 Ada 语言绑定 |
gnatlink | 一种工具,用于执行 Ada 语言连接 |
jc1 | 实际的 Java 编译程序 |
libgcc | 该库包含的例程被作为编泽程序的一部分,是因为它们可被连接到实际的可执行程序中。 它们是特殊的例程,连接到可执行程序,来执行基木的任务,例如浮点运算。这些库中的例程通常都是平台相关的 |
libgcj | 运行时库包含所有的核心 Java 类 |
libobjc | 对所有 Objective-C 程序都必须的运行时库 |
libstdc++ | 运行时库,包括定义为标准语言一部分的所有的 C++ 类和函数 |
表 2 列出的软件和 GCC 协同工作,目的是实现编译过程。有些是很基本的(例如 as 和 Id),而其他一些则是非常有用但不是严格需要的。尽管这些工具中的很多都是各种 UNIX 系统的本地工具,但还是能够通过 GNU 包 binutils 得到大多数工具。
工具 | 描述 |
---|---|
addr2line | 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻泽成源代码文 件名和行号。该程序是 binutils 包的一部分 |
ar | 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理连接程序使用的目标库文档。该程序是 binutils 包的一部分 |
as | GNU 汇编器。实际上它是一族汇编器,因为它可以被编泽或能够在各种不同平台上工作。 该程序是 binutils 包的一部分 |
autoconf | 产生的 shell 脚木自动配置源代码包去编泽某个特定版木的 UNIX |
c++filt | 程序接受被 C++ 编泽程序转换过的名字(不是被重载的),而且将该名字翻泽成初始形式。 该程序是 binutils 包的一部分 |
f2c | 是 Fortran 到C的翻译程序。不是 GCC 的一部分 |
gcov | gprof 使用的配置工具,用来确定程序运行的时候哪一部分耗时最大 |
gdb | GNU 调试器,可用于检查程序运行时的值和行为 |
GNATS | GNU 的调试跟踪系统(GNU Bug Tracking System)。一个跟踪 GCC 和其他 GNU 软件问题的在线系统 |
gprof | 该程序会监督编泽程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供 的配置文件来优化程序。该程序是 binutils 包的一部分 |
ld | GNU 连接程序。该程序将目标文件的集合组合成可执行程序。该程序是 binutils 包的一部 |
libtool | 一个基本库,支持 make 程序的描述文件使用的简化共享库用法的脚木 |
make | 一个工具程序,它会读 makefile 脚木来确定程序中的哪个部分需要编泽和连接,然后发布 必要的命令。它读出的脚木(叫做 makefile 或 Makefile)定义了文件关系和依赖关系 |
nlmconv | 将可重定位的目标文件转换成 NetWare 可加载模块(NetWare Loadable Module, NLM)。该 程序是 binutils 的一部分 |
nm | 列出目标文件中定义的符号。该程序是 binutils 包的一部分 |
objcopy | 将目标文件从一种二进制格式复制和翻译到另外一种。该程序是 binutils 包的一部分 |
objdump | 显示一个或多个目标文件中保存的多种不同信息。该程序是 binutils 包的一部分 |
ranlib | 创建和添加到 ar 文档的索引。该索引被 Id 使用来定位库中的模块。该程序是 binutils 包的一部分 |
ratfor | Ratfor 预处理程序可由 GCC 激活,但不是标准 GCC 发布版的一部分 |
readelf | 从 ELF 格式的目标文件显示信息。该程序是 binutils 包的一部分 |
size | 列出目标文件中每个部分的名字和尺寸。该程序是 binutils 包的一部分 |
strings | 浏览所有类型的文件,析取出用于显示的字符串。该程序是 binutils 包的一部分 |
strip | 从目标文件或文档库中去掉符号表,以及其他调试所需的信息。该程序是 binutils 包的一部 |
vcg | Ratfor 浏览器从文木文件中读取信息,并以图表形式显示它们。而 vcg 工具并不是 GCC 发布中的一部分,但 -dv 选项可被用来产生 vcg 可以理解的优化数据的格式 |
windres | Window 资源文件编泽程序。该程序是 binutils 包的一部分 |
GCC 、gcc和g++的区别
GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
gcc是GCC中的GUN C Compiler(C 编译器)
g++是GCC中的GUN C++ Compiler(C++编译器)
就本质而言,gcc和g++并不是编译器,也不是编译器的集合,它们只是一种驱动器,根据参数中要编译的文件的类型,调用对应的GUN编译器而已,比如,用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默认调用了C compiler,而g++默认调用了C++ compiler。
实际使用中我们更习惯使用 gcc 指令编译 C 语言程序,用 g++ 指令编译 C++ 代码。需要强调的一点是,这并不是 gcc 和 g++ 的区别,gcc 指令也可以用来编译 C++ 程序,同样 g++ 指令也可以用于编译 C 语言程序。
实际上,只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译。可以这样理解,gcc 是 GCC 编译器的通用编译指令,因为根据程序文件的后缀名,gcc 指令可以自行判断出当前程序所用编程语言的类别,比如:
- xxx.c:默认以编译 C 语言程序的方式编译此文件;
- xxx.cpp:默认以编译 C++ 程序的方式编译此文件。
- xxx.m:默认以编译 Objective-C 程序的方式编译此文件;
- xxx.go:默认以编译 Go 语言程序的方式编译此文件;
当然,gcc 指令也为用户提供了“手动指定代表编译方式”的接口,即使用 -x 选项。例如,gcc -xc xxx 表示以编译 C 语言代码的方式编译 xxx 文件;而 gcc -xc++ xxx 则表示以编译 C++ 代码的方式编译 xxx 文件。
但如果使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件。也就是说,对于 .c 文件来说,gcc 指令以 C 语言代码对待,而 g++ 指令会以 C++ 代码对待。但对于 .cpp 文件来说,gcc 和 g++ 都会以 C++ 代码的方式编译。
有读者可能会认为,C++ 兼容 C 语言,因此对于 C 语言程序来说,使用 gcc 编译还是使用 g++ 编译,应该没有什么区别,事实并非如此。严格来说,C++ 标准和 C 语言标准的语法要求是有区别的。举个例子:
#include <stdio.h> int main() { const char * a = "abc"; printStr(a); return; } int printStr(const char* str) { printf(str); }
如上所示,这是一段不规范的 C 语言代码。如果我们使用 gcc 指令编译,如下所示:
可以看到,该指令的执行过程并没有发生任何错误。而同样的程序,如果我们使用 g++ 指令编译,可以看到,GCC 编译器发现了 3 处错误。显然,C++ 标准对代码书写规范的要求更加严格。
除此之外对于编译执行 C++ 程序,使用 gcc 和 g++ 也是有区别的。要知道,很多 C++ 程序都会调用某些标准库中现有的函数或者类对象,而单纯的 gcc 命令是无法自动链接这些标准库文件的。举个例子:
#include <iostream> #include <string> using namespace std; int main(){ string str ="C语言中文网"; cout << str << endl; return 0; }
这是一段很简单的 C++ 程序,其通过 <string> 头文件提供的 string 字符串类定义了一个字符串对象,随后使用 cout 输出流对象将其输出。对于这段 C++ 代码,如果我们使用 g++ 指令编译,如下所示:
可以看到,使用g++可以正常编译,但使用gcc无法正常编译。其根本原因就在于,该程序中使用了标准库 <iostream> 和<string> 提供的类对象,而 gcc 默认是无法找到它们的。如果想使用 gcc 指令来编译执行 C++ 程序,需要在使用 gcc 指令时,手动为其添加 -lstdc++ -shared-libgcc 选项,表示 gcc 在编译 C++ 程序时可以链接必要的 C++ 标准库。也就是说,我们可以这样编译 demo.cpp 文件:
显然通过gcc来编译书写起来更加麻烦,于 C 语言程序的编译,我们应该使用 gcc 指令,而编译 C++ 程序则推荐使用 g++ 指令,这就足够了。
GCC自动识别扩展名
通过前面的学习我们知道,对于执行 C 或者 C++ 程序,需要借助 gcc(g++)指令来调用 GCC 编译器。并且对于以 .c 为扩展名的文件,GCC 会自动将其视为 C 源代码文件;而对于以 .cpp 为扩展名的文件,GCC 会自动将其视为 C++ 源代码文件。
除此之外,GCC 编译器还可以自动识别多种扩展名(如表 3 所示),即根据不同的扩展名确定该文件该怎样编译。
表 3GCC 自动识别的常用扩展名
文件名称+扩展名 | GCC 编译器识别的文件类型 |
file.c | 尚未经过预处理操作的 C 源程序文件。 |
file.i | 经过预处理操作、但尚未进行编译、汇编和连接的 C 源代码文件 |
file.cpp file.cp file.cc file.cxx file.CPP file.c++ file.C |
尚未经过预处理操作的 C++ 源代码文件。 |
file.ii | 已经预处理操作,但尚未进行编译、汇编和连接的 C++ 源代码文件 |
file.s | 经过编译生成的汇编代码文件 |
file.h | C、C++ 或者 Objective-C++ 语言头文件 |
file.hh file.H file.hp file.hxx file.hpp file.HPP file.h++ file.tcc |
C++ 头文件 |
注意,表 1 仅罗列了 GCC 编译器可识别的与 C 和 C++ 语言相关的文件后缀名。除此之外,GCC 编译器还支持 Go、Objective-C,Objective-C ++,Fortran,Ada,D 和 BRIG(HSAIL)等编程语言的编译,关于这些编程语言可被识别的文件扩展名,感兴趣的读者可前往GCC官网查看。
有读者可能会问,如果当前文件的扩展名和表 1 不符,还能使用 GCC 编译器吗?答案是肯定的。只需要借助 -x 选项(小写)指明当前文件的类型即可。例如:一个C程序,存储在名为demo的文件中:
//存储在 demo 文件中 #include <stdio.h> int main(){ puts("GCC教程:http://c.biancheng.net/gcc/"); return 0; }
显然,这是一段完整的 C 语言程序,但由于其存储在无扩展名的 demo 文件中,如果直接使用 gcc 指令调用 GCC 编译器,则执行会报错:
可以看到,GCC 编译器无法识别 demo 这个文件。这种情况下,就必须使用 -x 选项手动为其指定文件的类型,例如:
通过为 gcc 指令添加 -xc 选项,表明当前 demo 为 C 语言程序文件,由此 GCC 编译器即可成功将其编译为 a.out 可执行文件。除了用 c 表明 C 语言程序文件外,-x 指令还是后跟 c-header(C语言头文件)、c++(C++源文件)、c++-header(C++程序头文件)等选项。
GCC -std编译标准
任何一门语言都在不停的维护和更新,以 C 语言为例,发展至今该编程语言已经迭代了诸多个版本,例如 C89(偶尔又称为 C90)、C94(C89 的修订版)、C99、C11、C17,以及当下正在开发的 C2X 新标准。甚至于在这些标准的基础上,GCC 编译器本身还对 C 语言的语法进行了扩展,先后产生了 GNU90、GNU99、GNU11 以及 GNU17 这 4 个版本。
C++ 语言的发展也历经了很多个版本,包括 C++98、C++03(C++98 的修订版)、C++11(有时又称为 C++0x)、C++14、C++17,以及即将要发布的 C++20 新标准。和 C 语言类似,GCC 编译器本身也对不同的 C++ 标准做了相应的扩展,比如 GNU++98、GNU++11、GNU++14、GNU++17。
不同版本的 GCC 编译器,默认使用的标准版本也不尽相同。以当前最新的 GCC 10.1.0 版本为例,默认情况下 GCC 编译器会以 GNU11 标准(C11 标准的扩展版)编译 C 语言程序,以 GNU++14 标准(C++14 标准的扩展版)编译 C++ 程序。
们可以手动控制 GCC 编译器使用哪个编译标准吗?答案是肯定的,对于编译 C、C++ 程序来说,借助 -std 选项即可手动控制 GCC 编译程序时所使用的编译标准。也就是说,当使用 gcc 指令编译 C 语言程序时,我们可以借助 -std 选项指定要使用的编译标准;同样,当使用 g++ 指令编译 C++ 程序时,也可以借助 -std 选项指定要使用的编译标准。注意,不同版本的 GCC 编译器,所支持使用的 C/C++ 编译标准也是不同的。
举个例子,如下是一个 C 语言源程序:
#include <stdio.h> int main(){ for(int i=0;i<10;i++){ printf("i=%d ",i); } }
如果我们想以 c99 的标准编译它,在确认当前所有 GCC 编译器版本支持 C99 标准的前提下,通过执行如下指令,即可完成编译:
但是,对于在 for 循环中声明变量 i 的做法,是违反 C89 标准的。也就是说,如果我们以 C89 的编译标准编译 main.c,GCC 编译器会报错:
在编写程序前必须明确要使用的编译标准,并清楚得知道该标准下什么可用,什么不可用。