以下内容源于C语言中文网的学习与整理,如有侵权请告知删除。
参考内容
(1)C语言程序设计门户网站(入门教程、编程软件)
(2)GCC官方文档网址
一、GCC编译器的由来
谈到 GCC编译器,就不得不提 GNU 计划。GNU 计划的最终目标是打造出一套完全自由与开源的操作系统,并初步将其命名为 GNU 操作系统。虽然该计划为 GNU 操作系统量身定做了名为 Thr Hurd 的系统内核,但由于其性能比不上同时期诞生的 Linux 内核,最终 GNU 计划放弃 The Hurd 而选用 Linux 作为 GNU 操作系统的内核。因此其被称为 GNU/Linux 操作系统(人们更习惯称为 Linux 操作系统)。在 Linux 内核的基础上,GNU 计划开发了很多系统部件,GCC 就是其中之一。
早期 GCC 的全拼为 GNU C Compiler,即 GUN 计划诞生的 C 语言编译器,显然最初 GCC 的定位确实只用于编译 C 语言。但经过这些年不断的迭代,GCC 的功能得到了很大的扩展,它不仅可以用来编译 C 语言程序,还可以处理 C++、Go、Objective -C 等多种编译语言编写的程序。与此同时,由于之前的 GNU C Compiler 已经无法完美诠释 GCC 的含义,所以其英文全称被重新定义为 GNU Compiler Collection,即 GNU 编译器套件。
所谓编译器,可以简单地将其理解为“翻译器”。要知道,计算机只认识二进制指令(仅有 0 和 1 组成的指令),我们日常编写的 C 语言代码、C++ 代码、Go 代码等,计算机根本无法识别,只有将程序中的每条语句翻译成对应的二进制指令,计算机才能执行。
GCC 编译器从而停止过改进。截止到今日(2020 年 5 月),GCC 已经从最初的 1.0 版本发展到了 10.1 版本,期间历经了上百个版本的迭代。作为一款最受欢迎的编译器,GCC 被移植到数以千计的硬件/软件平台上,几乎所有的 Linux 发行版也都默认安装有 GCC 编译器,但通常版本比较低,如果需要更新,看第二点“GCC编译器下载与安装”。
操作系统大致分为 2 大阵营,分别是 Windows 阵营和类 Unix 阵营(包括 Unix、Linux、Mac OS、安卓等)。通常情况下,Windows 系统下用户更习惯使用现有的 IDE 来编译程序;而类 Unix 系统下,用户更喜欢直接编写相应的 gcc 指令来编译程序。纯 GCC 编译器没有我们熟悉的界面窗口,要想使用它必须编写对应的 gcc 指令;而集成了 GCC 编译器的开发软件(IDE),在集成 GCC 编译器功能的同时,还向用户提供友好的界面窗口,使得用户即便记不住 gcc 指令,也能从事开发工作。
二、GCC编译器的构成部分
表1列出的是GCC的组成部分。
部分 | 描述 |
---|---|
c++ | gcc的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。 |
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++ 库。 |
gcc | 该驱动程序等同于执行编译程序和连接程序以产生需要的输出。 |
gcj | 该驱动程序用于编译java。 |
gnat1 | 实际的 Ada 编译程序。 |
gnatbind | 一种工具,用于执行 Ada 语言绑定。 |
gnatlink | 一种工具,用于执行 Ada 语言连接。 |
jc1 | 实际的 Java 编译程序。 |
libgcc |
这是一个库。该库包含的例程被作为编译程序的一部分,是因为它们可被连接到实际的可执行程序中。它们是特殊的例程,连接到可执行程序,来执行基本的任务,例如浮点运算。这些库中的例程通常都是平台相关的。 |
libgcj | 运行时库包含所有的核心 Java 类。 |
libobjc | 对所有 Objective-C 程序都必须的运行时库。 |
libstdc++ | 运行时库,包括定义为标准语言一部分的所有的 C++ 类和函数 |
表 2 列出的软件和 GCC编译器协同工作,目的是实现编译过程。有些是很基本的(例如 as 和 Id),而其他一些则是非常有用但不是严格需要的。可以通过 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编译器与C语言编译标准
以 C 语言为例,发展至今该编程语言已经迭代了诸多个版本,例如 C89(偶尔又称为 C90)、C94(C89 的修订版)、C99、C11、C17,以及当下正在开发的 C2X 新标准。
甚至于在这些标准的基础上,GCC 编译器本身还对 C 语言的语法进行了扩展,先后产生了 GNU90、GNU99、GNU11 以及 GNU17 这 4 个版本。有趣的是,GCC 编译器对 C 语言的很多扩展,往往会被 C 语言标准委员会所采纳,并添加到新的 C 语言标准中。例如,GNU90 中对 C 语言的一些扩展,就融入到了新的 C99 标准中;GNU90、GNU99 中对 C 语言的一些扩展,被融入到了新的 C11 标准中。
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 编译器使用哪个编译标准。当然,这也要看该版本的GCC编译器是否支持某个编译标准,下表1和表2是不同版本的GCC编译器支持的编译标准。另外,有些GCC编译器版本对应的同一编译标准有 2 种表示方式,例如对于 8.4~10.1 版本的 GCC 编译器来说,-std=c89 和 -std=c90 是一样的,使用的都是 C89/C90 标准。
GCC 版本 | C语言常用标准 | |||||||
---|---|---|---|---|---|---|---|---|
C89/C90 | C99 | C11 | C17 | GNU90 | GNU99 | GNU11 | GNU17 | |
10.1 ~ 8.4 | c89/c90 | c99 | c11 | c17/c18 | gnu90/gnu89 | gnu99 | gnu11 | gnu17/gnu18 |
7.5 ~ 5.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.9.4 ~ 4.8.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.7.4 | c89/c90 | c99) | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.6.4 | c89/c90 | c99 | c1x | gnu90/gnu89 | gnu99 | gnu1x | ||
4.5.4 | c89/c90 | c99 | gnu90/gnu89 | gnu99 |
GCC 版本 | C++常用标准 | |||||||
---|---|---|---|---|---|---|---|---|
C++98/03 | C++11 | C++14 | C++17 | GNU++98 | GNU++11 | GNU++14 | GNU++17 | |
10.1 ~ 8.4 | c++98/c++03 | c++11 | c++14 | c++17 | gnu++98/gnu++03 | gnu++11 | gnu++14 | gnu++17 |
7.5 ~ 5.5 | c++98/c++03 | c++11 | c++14 | c++1z | gnu++98/gnu++03 | gnu++11 | gnu++14 | gnu++1z |
4.9.4 ~ 4.8.5 | c++98/c++03 | c++11 | c++1y | gnu++98/gnu++03 | gnu++11 | gnu++1y | ||
4.7.4 | c++98 | c++11 | gnu++98 | gnu++11 | ||||
4.6.4 | c++98 | c++0x | gnu++98 | gnu++0x | ||||
4.5.4 | c++98 | c++0x | gnu++98 | gnu++0x |
在编译 C/C++ 程序时,可以借助 -std 选项手动去控制 GCC 编译程序时所使用的编译标准。
xjh@ubuntu:~/iot/tmp$ gcc -std=c99 demo.c -o demo.exe
xjh@ubuntu:~/iot/tmp$ ls
demo.exe demo.c
xjh@ubuntu:~/iot/tmp$
四、GCC编译器识别不同扩展名的文件
简而言之,如果有可识别的扩展名则可以识别,没有扩展名则需要利用-x选项进行声明。
在对 C 或者 C++ 程序进行编译时,需要借助 gcc 或者 g++指令来调用 GCC 编译器。
此时,对于以 .c 为扩展名的文件,GCC编译器会自动将其视为 C 源代码文件。对于以 .cpp 为扩展名的文件,GCC编译器会自动将其视为 C++ 源代码文件。
下面表 1 罗列了 GCC 编译器可识别的与 C 和 C++ 语言相关的文件后缀名。
文件名称+扩展名 | 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 不符,则需要借助 -x 选项指明当前文件的类型。这些选项可以是c(C源文件)、c-header(C头文件)、c++(C++源文件)、c++-header(C++头文件)等选项。
比如,如果C 程序存储在无扩展名的 demo 文件中,直接使用 gcc 指令调用 GCC 编译器会报错,必须使用 -x 选项手动为其指定文件的类型,即通过为 gcc 指令添加 -xc 选项,表明当前 demo 为 C 语言程序文件,即可成功将其编译为 a.out 可执行文件。
xjh@ubuntu:~/iot/tmp$ ls
demo
xjh@ubuntu:~/iot/tmp$ gcc demo
demo: file not recognized: 不可识别的文件格式
collect2: error: ld returned 1 exit status
xjh@ubuntu:~/iot/tmp$ gcc -xc demo
xjh@ubuntu:~/iot/tmp$ ls
a.out demo
xjh@ubuntu:~/iot/tmp$ ./a.out
hello world!
xjh@ubuntu:~/iot/tmp$
五、gcc指令和g++指令的区别
简而言之,推荐使用 gcc 指令编译C程序,推荐使用 g++ 指令编译C++程序。
对于已编辑好的 C 或者 C++ 源代码,可以通过执行 gcc 或者 g++ 指令来调用 GCC 编译器。实际使用中我们更习惯使用 gcc 指令编译 C 语言程序,用 g++ 指令编译 C++ 代码。但其实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++ 程序,使用 gcc 和 g++ 也是有区别的。要知道,很多 C++ 程序都会调用某些标准库中现有的函数或者类对象,而单纯的 gcc 指令是无法自动链接这些标准库文件的。如果想使用 gcc 指令来编译执行 C++ 程序,需要在使用 gcc 指令时,手动为其添加 -lstdc++ -shared-libgcc 选项,表示 gcc 在编译 C++ 程序时可以链接必要的 C++ 标准库。比如:
xjh@ubuntu:~/iot/tmp$ ls
demo #当前文件类型 #没有后缀名的文件 #要链接的库
xjh@ubuntu:~/iot/tmp$ gcc -xc++ demo -lstdc++ -shared-libgcc
xjh@ubuntu:~/iot/tmp$ ls
a.out demo
xjh@ubuntu:~/iot/tmp$
六、GCC编译器与CC编译器的区别
1、名字与作用差异
GCC编译器与CC编译器,它们都是编译器,用于将高级语言转换成低级语言。
CC的全称为“C Compiler”,它是Unix系统用来编译C语言的编译器,只支持C语言的编译。
GCC的全称是“Gnu Compiler Collection”,是很多编译器的集合,比如C编译器、 C++编译器、Objective-C编译器、Fortran编译器和Java编译器等等。当调用gcc命令时,GCC编译器会根据文件扩展名自动识别并调用对应的编译器。
2、符号连接cc、变量CC
(1)在Linux系统中,小写的cc是一个符号链接,默认指向gcc命令,如下所示。
xjh@ubuntu:~$ ls -l /usr/bin/cc
lrwxrwxrwx 1 root root 20 十月 18 2017 /usr/bin/cc -> /etc/alternatives/cc
xjh@ubuntu:~$ ls -l /etc/alternatives/cc
lrwxrwxrwx 1 root root 12 十月 18 2017 /etc/alternatives/cc -> /usr/bin/gcc
xjh@ubuntu:~$
(2)大写的CC是make程序的一个内建变量(或者说“隐含变量”,见博客Makefile隐含规则),换句话说,就算在Makefile中没有定义CC=xxx,该变量也会存在,其值为cc,即默认指向gcc命令。
xjh@ubuntu:~/iot/tmp$ cat Makefile
ALL:@echo ${CC} #这里是大写的CC
xjh@ubuntu:~/iot/tmp$ make
cc #输出是小写的cc,也就是变量CC的值是cc,即CC = cc
xjh@ubuntu:~/iot/tmp$
(3)符号链接cc与变量CC存在的意义在于源码的移植性。我们可以使用GCC编译器来编译之前用CC编译器编译的unix软件,甚至连Makefile都不要改;反过来也便于linux程序在unix下的编译。
3、CC编译器和GCC编译器的取舍
CC只在Unix和类Unix操作系统上使用,而GCC可以在各种操作系统上使用,支持的新特性也多,因此没必要再使用CC。