GCC 编译器----通史

计算机软件作为人类的知识财富,为人类社会的发展起到了巨大的作用,但长期以来软件源码作为个人或公司的私有财产受到严格的保密,很难做到像文学艺术作品一样地进行公开的交流,很大程度上造成软件的低水平,重复劳动严重,在一定意义上制约了软件的发展。

1. GPL与GNU

  • 直到 1985 年由 MIT 教授理查德·斯托曼(Richard Stallman)提出应将软件源码看成人类共同拥有的知识财富,应该公开地自由交换、修改,提出了 GNU 计划(因英文名相同,GNU 的 logo 就是一只牛羚),并建立了自由软件基金会;同时,发布了一份举足轻重的法律文件,GNU 通用公共授权书(GNU GPL, GNU General Public License)。

该授权书主要有以下几点:

自由软件(free software)指的是源码自由,不是价格;
自由软件必须附带程序源代码,但可收取费用;
任何人都可以自由分发自由软件并收取费用,但必须列明原创者姓名;
任何人都可以修改源代码,但必须列明修改人名字,以保护原创者名誉;
任何人都可以采用源代码中的某一段,但其开发之软件必须也为自由软件(例如,如果 Netscap 是自由软件,而 IE 采用了其中的部份源代码,则 IE 也必须成为自由软件);
任何自由软件的衍生品也必须是自由软件;
自由软件没有担保,以保护分发者。
  • 1991 年,Richard Stallman 对授权做了微小的修改,即所谓的通用公共授权第 2 版。同时,他也推出了更宽松的通用公共授权,用于自由程序库。这一系列的授权有效地保护了自由软件不受商业软件的非法侵犯,例如,1998 年 Netscap 决定采用与 GPL 差不多的 NPL(Netscap Public Liscense),这样一来,Microsoft 就无法将 Netscap 中的源代码运用在 IE 上,除非它们也要成为自由软件。

  • 至此,在 GPL 下人们就可以自由交流、修改软件源码了,这一协议极大地推动了整个计算机软件行业的发展,并带来了以下明显的益处:

  • 对于广大计算机软件的学习者来说,可以直接从源码中吸取营养,缩短学习的时间,提高学习的效率,少走弯路,再也不必花大量时间去看那些不知正确与否的“未解之谜”了,学习在某种程度上变成了一件轻松愉快的事情了。

  • 可以集中大家的智慧发展软件,避免重复劳动。一个软件只有公开源码,通过很多人的研究才有可能发现其中深藏的错误,大家才能公开探讨相关的问题,并进行改进,在大家的共同“挑剔与监督”下才有可能编写出尽善尽美的软件来。

  • GPL 协议的核心就是要对源码进行公开,并且允许任何人修改源码,但是只要使用了 GPL 协议的软件源码,其衍生软件也必须公开源码,准许其他人阅读和修改源码,即 GPL 协议具有继承性。

  • 另一个问题就是 GPL 软件并非就是免费软件,这里所说的自由软件是指对软件源码的自由获得与自由使用、修改,软件开发者不但可以通过服务来收费,而且还可以通过出售 GPL 软件来获利。

  • 适应 GPL 协议的软件一般都是自由软件,自由软件是指一件可以让用户自由复制、使用、研究、修改、分发等,而不附带任何条件的软件。
    copyleft 授权

  • Stallman 为了停止中间人对自由软件权利的侵害,提出了 copyleft 授权,因为自由软件在发布过程中可能会有一些不合作的人通过对程序的修改而将软件变成私有软件,将程序变成 copyleft 授权。

  • 我们首先声明它是有版权的,而后加人了分发条款,这些条款是法律指导,使得任何人都拥有对这一程序代码或者任何这一程序的衍生品的使用、修改和重新发布的权力,但前提是这些发布条款不能被改变。这样在法律上,代码和自由就不可分割了。

  • 自由软件的支持者相信,总有一天,随着自由软件的日渐成熟,自由软件终将主宰整个软件行业,人们不再受少数商业软件公司的控制,真正实现“市集式开发模式”。

2. GCC发展史

  • GNU 项目计划的主要目的是创建一个名叫 GNU’s Not Unix(GNU) 的完全免费的操作系统。该操作系统将包括绝大多数自由软件基金会所开发的其他软件,以对抗所有商业软件,而这个操作系统的核心(kernel)就叫 HURD。

  • 但是 GNU 在开发完全免费的操作系统上并未取得成功,直到 20 世纪 90 年代由林纳斯·本纳第克特·托瓦兹(Linus Benedict Torvalds)开发了 Linux 操作系统,GNU 才算在免费操作系统上完成了任务。

  • 虽然 GNU 计划在开发免费操作系统上不成功,但是却成功开发几个广为流传的 GNU 软件,其中最著名的是 GNU C Complier(gcc)。

  • 这个软件成为历史上最优秀的C语言编译器, 其执行效率与一般的编译器相比平均效率要高 20%~30%,使得那些靠贩卖编译器的公司大吃苦头,因为它们无法研制出与 gcc 同样优秀,却又完全免费、并开放源代码的编译器来。

  • 而由于它又是 copylefted,所以一旦有用户发现错误,就会通知 Richard Stallman,所以几乎每个月都可以推出新版本。然而,它还有一个十分特殊而且不同寻常的意义:几乎所有的自由软件都是通过它编译的。可以说,它是自由软件发展的基石与标杆。

  • 现在,gcc 已经可以支持 7 种编程语言和 30 种编程结构,是学术界最受欢迎的编译工具。

  • 其他 GNU 软件还包括 GNU emacs、GNU Debugger(GDB)、GNU Bash 以及大部分 Linux 系统的程序库和工具等。

  • 目前,gcc 已发展到了 8.x 的版本,几乎所有开源软件和自由软件中都会用到,因此它的编译性能会直接影响到 Linux、Firefox、OpenOffice.org、Apache 以及一些数不清的小项目的开发。gcc 无疑处在开源软件的核心地位。

  • 作为自由软件的旗舰项目,Richard Stallman 在十多年前刚开始写作 gcc 的时候,还只是把它当作一个C程序语言的编译器;gcc 的意思也只是 GNU C Compiler 而已。经过这么多年的发展,gcc 已经不仅仅能支持C语言,它现在还支持 Ada、C++、Java、Objective-C、Pascal、COBOL 以及函数式编程和逻辑编程的 Mercury 语言等。因此,现在的 gcc 已经变成了 GNU Compiler Collection,也即是 GNU 编译器家族的意思了。这个名称同时也说明了 gcc 对于各种硬件平台无所不在的支持,甚至包括一些生僻的硬件平台。

  • gcc 不仅功能非常强大,结构也异常灵活。最值得称道的一点就是,它可以通过不同的前端模块来支持各种语言,如 Java、Fortran、Pascal、Modula-3 和 Ada 语言等。
    总结

  • GUN 虽然没有开发出操作系统,但是却开发出了很多系统级的自由软件,GCC 就是其中之一。

3. GCC所支持的平台

  • GCC 编译程序集合可以在很多平台上运行。平台是指特定计算机芯片(CPU)及其运行的操作系统的组合。

  • 尽管 GCC 已经被移植到数以千计的平台(硬件/软件的组合)上,但只有一些基木平台可以用来测试发布的正确性。表1中列出的平台是最流行的,并且它们对 GCC 支持的最好。
    在这里插入图片描述


  • GCC 除了可以保证在表 1 中列出的主要平台上正确运行,也能很好地支持表 2 中列出的次要平台。
    在这里插入图片描述

  • 以上平台都是 GCC 进行了良好测试的平台。相比数以千计的平台,这些平台显然很少,这主要是因为人力和资金问题。

  • 即使你的平台没有被列在上面,GCC 还是可能会运行地很好。GCC 的源代码中提供了完整的测试集合,你可以在自己的平台上自行测试。

  • 另一种方法就是作为志愿者来测试你的平台,这样 GCC 就可以在每次发布之前得到测试。

4. GCC的组成部分以及使用到的软件

  • GCC 是由许多组件组成的。表 1 列出了 GCC 的各个部分,但它们也并不总是出现 的。有些部分是和语言相关的,所以如果没有安装某种特定语言,系统:中就不会出现相关的文件。
    在这里插入图片描述

  • 表 2 列出的软件和 GCC 协同工作,目的是实现编译过程。有些是很基本的(例如 as 和 Id),而其他一些则是非常有用但不是严格需耍的。尽管这些工具中的很多都是各种 UNIX 系统的本地共具,但还是能够通过 GNU 包 binutils 得到大多数工具。
    在这里插入图片描述

5. GCC编译C语言程序

  • GCC 仅仅是一个编译器,没有界面,必须在命令行模式下使用。通过gcc命令就可以将源文件编译成可执行文件。

  • GCC 既可以一次性完成C语言源文件的编译,也可以分步骤完成。本节将完整演示如何一次性完成源文件的编译(初学者也经常会这么做),下节将演示分步骤编译源文件。

本节以下面的C语言代码为例进行演示:

#include <stdio.h>
int main()
{
    puts("C语言中文网");
    return 0;
}
  1. 生成可执行程序
    最简单的生成可执行文件的写法为:

$ cd demo #进入源文件所在的目录
$ gcc main.c #在 gcc 命令后面紧跟源文件名

#表示注释,读者可以不写,我写上是为了让读者明白每个命令的含义。#是 Shell 中的注释格式。

这样就一次性完成了编译和链接的全部过程,非常方便。

注意:不像 Windows,Linux 不以文件后缀来区分可执行文件,Linux 下的可执行文件后缀理论上可以是任意的,这里的.out只是用来表明它是 GCC 的输出文件。不管源文件的名字是什么,GCC 生成的可执行文件的默认名字始终是a.out。

如果不想使用默认的文件名,那么可以通过-o选项来自定义文件名,例如:

$ gcc main.c -o main.out
这样生成的可执行程序的名字就是main.out。

因为 Linux 下可执行文件的后缀仅仅是一种形式上的,所以可执行文件也可以不带后缀,例如:

$ gcc main.c -o main
这样生成的可执行程序的名字就是main。

通过-o选项也可以将可执行文件输出到其他目录,并不一定非得在当前目录下,例如:

$ gcc main.c -o ./out/main.out
或者

$ gcc main.c -o out/main.out
表示将可执行文件输出到当前目录下的out目录,并命名为main.out。./表示当前目录,如果不写,默认也是当前目录。

注意:out 目录必须存在,如果不存在,gcc 命令不会自动创建,而是抛出一个错误。
  1. 运行可执行程序
    上面我们生成了可执行程序,那么该如何运行它呢?很简单,在控制台中输入程序的名字就可以,如下所示:

$ ./a.out
./表示当前目录,整条命令的意思是运行当前目录下的 a.out 程序。如果不写./,Linux 会到系统路径下查找 a.out,而系统路径下显然不存在这个程序,所以会运行失败。

所谓系统路径,就是环境变量指定的路径,我们可以通过修改环境变量添加自己的路径,或者删除某个路径。很多时候,一条 Linux 命令对应一个可执行程序,如果执行命令时没有指明路径,那么就会到系统路径下查找对应的程序。

输入完上面的命令,按下回车键,程序就开始执行了,它会将输出结果直接显示在控制台上,如下所示:

$ cd demo
$ gcc main.c
$ ./a.out
C语言中文网
$

6. GCC编译流程

GCC 编译器在编译一个C语言程序时需要经过以下 4 步:

  1. 将C语言源程序预处理,生成.i文件。
  2. 预处理后的.i文件编译成为汇编语言,生成.s文件。
  3. 将汇编语言文件经过汇编,生成目标文件.o文件。
  4. 将各个模块的.o文件链接起来生成一个可执行程序文件。

GCC编译流程如下图所示。
在这里插入图片描述


.i文件、.s文件、.o文件可以认为是中间文件或临时文件,如果使用 GCC 一次性完成C语言程序的编译,那么只能看到最终的可执行文件,这些中间文件都是看不到的,因为 GCC 已经经它们删除了。

当然,可以使用 GCC 选项看到这些中间文件,下节我们会讲解 GCC 选项。

7.GCC常用选项

gcc 是一个功能强大的编译器,其编译选项非常多。有些选项一般程序员根本不会用到。因此将所有的编译选项全部列出讲解是不明智的。下面只对一些 gcc 编译器的常 用选项进行详细的讲解,这些选项在实际编程过程中非常实用。gcc 的常用选项如下表所示。

在这里插入图片描述


注意:gcc 编译选项会区分大小写。因此-o选项和-O选项的效果是不一样的。前者表示源文件编译成为可执行文件,后者表示将源文件编译成为可执行文件并且进行一级优化。

由于篇幅限制,本节只介绍几个简单的选项,复杂的选项会在后面几节中详细讲解。
-S
将C语言源文件编译为汇编语言,但是并不汇编该程序。使用该选项,我们可以查看C语言代码对应的汇编代码。
-E 选项
-E选项将C语言源文件进行预处理,但是并不编译该程序。对于一般的预处理问题,可以使用这个选项进行查看,例如,宏的展开问题、文件的包含问题等。
-I 选项
由于指定包含的头文件的目录,这一点对于大型的代码组织来说是很有用的。
-g 选项
-g选项可生成能被 gdb 调试器所使用的调试信息。只有使用了该选项后生成的可执行文件,才带有程序中引用的符号表。这时 gdb 调试程序才能对该可执行程序进行调试。

8.GCC -c 选项(只编译不链接)

-c选项表示编译、汇编指定的源文件(也就是编译源文件),但是不进行链接。使用-c选项可以将每一个源文件编译成对应的目标文件。

目标文件是一种中间文件或者临时文件,如果不设置该选项,gcc 一般不会保留目标文件,可执行文件生成完成后就自动删除了。

下面实例演示了 gcc -c 选项的用法。

$gcc -c test1.c test2.c test3.c
$ls -l *.o
-rwxr–r-- 1 root 23 Feb 7 02:57 test1.o
-rwxr–r-- 1 root 17 Feb 7 02:57 test2.o
-rwxr–r-- 1 root 20 Feb 7 02:57 test3.o

如果不使用-c选项,则仅仅生成一个可执行文件,没有目标文件。

注意,使用-c选项表示只编译源文件,而不进行链接,因此,对于链接中的错误是无法发现的。

下面例子演示了 gcc 编译器在使用-c选项的时候不会发现链接错误。

  1. 编写如下的两个源文件。

在 func.c 中定义了 func_a() 函数:

#include <stdio.h>
void func_a(){
    printf("FUNC_A\n");
}

在 main.c 中调用了 func_a() 和 func_b() 函数:

#include <stdio.h>
int main(void)
{
    func_a();
    func_b();
    return 0;
}

func_b() 函数并没有定义,所以在链接时会产生错误(编译时不会产生错误)。

  1. 使用-c选项编译两个源文件,如下所示:

$gcc -c func.c main.c
编译器没有输出任何错误信息。

  1. 不使用-c选项编译两个源文件:

$gcc func.c main.c
会看到如下的报错信息:

/tmp/ccLlOhvh.o:在函数‘main’中:
main.c:(.text+0x14):对‘func_b’未定义的引用
collect2: 错误:ld 返回 1
由于没有找到 func_b() 函数的定义,所以发生了链接错误。

9. GCC -o选项(生成可执行文件)

-o选项用来生成二进制可执行文件,它的格式为:

[file1] -o [file2]
[file1] 表示输入文件(也即要处理的文件),它可以是源文件,也可以是汇编文件或者是目标文件;[file2] 表示输出文件(也即处理的结果),也就是生成的可执行文件。

[file1] 和 [file2] 可以是一个文件,也可以是一组文件:

如果 [file1] 是一组文件,那么就表示有多个输入文件;
如果 [file2] 是一组文件,那么相当于生成了该二进制可执行文件的多个副本。

将源文件作为输入文件
现在有 func.c 和 main.c 两个源文件。其中 func.c 的内容为:

#include <stdio.h>
void func(){
    printf("http://c.biancheng.net\n");
}

main.c 的内容为:

#include <stdio.h>
int main(void)
{
    func();
    return 0;
}

下面对 func.c 和 main.c 进行编译:

$gcc main.c func.c -o app
编译完成后,使用 ls 命令查看生成的文件:

-rwxrwxr-x. 1 mozhiyan mozhiyan 8572 9月 26 14:00 app
-rw-rw-r–. 1 mozhiyan mozhiyan 60 9月 26 13:33 func.c
-rw-rw-r–. 1 mozhiyan mozhiyan 66 9月 26 13:59 main.c

func.c 和 main.c 两个源文件被编译、链接后生成了一个名为 app 的可执行文件。

可以通过下面的方式来执行 app 程序:

$ ./app
http://c.biancheng.net
将目标文件作为输入文件
gcc 的输入文件不仅仅可以是C语言源文件(.c文件),也可以是编译好的目标文件(.o文件)。

首先使用-c选项来生成两个目标文件:

$gcc -c func.c main.c
然后将两个目标文件作为输入文件:

$gcc func.o main.o -o app
使用 ls 命令来查看整个过程中生成的所有文件:

-rwxrwxr-x. 1 mozhiyan mozhiyan 8572 9月 26 14:00 app
-rw-rw-r–. 1 mozhiyan mozhiyan 60 9月 26 13:33 func.c
-rw-rw-r–. 1 mozhiyan mozhiyan 1488 9月 26 13:33 func.o
-rw-rw-r–. 1 mozhiyan mozhiyan 66 9月 26 13:59 main.c
-rw-rw-r–. 1 mozhiyan mozhiyan 1432 9月 26 13:33 main.o

最后运行生成的 app 程序:

$ ./app
http://c.biancheng.net

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页