Linux——GCC编译器
什么是GCC
早期 GCC 的全拼为 GNU C Compiler,即 GUN 计划诞生的 C 语言编译器。
GCC 的功能得到了很大的扩展,它不仅可以用来编译 C 语言程序,还可以处理 C++、Go、Objective -C 等多种编译语言编写的程序。
重新定义为 GNU Compiler Collection,即 GNU 编译器套件。
GCC安装查询
使用命令查询GCC的版本:
若以安装gcc,如下:
未安装gcc,则执行结果如下:
GCC的组成部分
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++ 类和函数 |
GCC使用到的软件
GCC 使用的软件工具
工具 | 描述 |
---|---|
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、C++ 程序,其本意指的是将 C、C++ 代码转变为可执行程序(等同于 Windows 系统中以 .exe 为后缀的可执行文件)。
程序从源代码生成可执行程序的过程,需经历 4 个过程,分别是预处理、编译、汇编和链接。
示例:
如下一段C语言程序:
//该程序存储在 test.c 文件中
#include <stdio.h>
int main()
{
printf("GCC编译示例");
return 0;
}
打开命令行窗口,编写如下 gcc 指令:
[root@yan ~]# gcc test.c
执行Enter 回车键,此时GCC编译器在当前目录下生成了一个对应的可执行的文件,即 a.out ;可执行ls 查看文件是否存在
[root@yan ~]# ls
a.out test.c
可以执行该文件,查看运行结果,如下:
[root@yan ~]# ./a.out
GCC编译示例
gcc 指令还支持用户手动指定最终生成的可执行文件的文件名
[root@yan ~]# gcc test.c -o test.exe
其中 -o 选项用于指定要生成的文件名。
GCC常用的编译选项
GCC选项 | 描述 |
---|---|
-E(大写) | 预处理指定的源文件,不进行编译。 |
-S(大写) | 编译指定的源文件,但是不进行汇编。 |
-c | 编译、汇编指定的源文件,但是不进行链接。 |
-o | 指定生成文件的文件名。 |
-llibrary(-I library) | 其中 library 表示要搜索的库文件的名称。该选项用于手动指定链接环节中程序可以调用的库文件。建议 -l 和库文件名之间不使用空格,比如 -lstdc++。 |
-ansi | 对于 C 语言程序来说,其等价于 -std=c90;对于 C++ 程序来说,其等价于 -std=c++98。 |
-std= | 手动指令编程语言所遵循的标准,例如 c89、c90、c++98、c++11 等。 |
GCC -E选项:预处理
gcc 指令添加 -E 选项,即可控制 GCC 编译器仅对源代码做预处理操作。
所谓预处理操作,主要是处理那些源文件和头文件中以 # 开头的命令(比如 #include、#define、#ifdef 等),并删除程序中所有的注释 // 和 /* ... */。
默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。
与-o 选项连用,将结果导入到指令的文件中,如:
# gcc -E test.c -o test.i
Linux 系统中通常用 “.i” 作为 C 语言程序预处理后所得文件的后缀名。
gcc 指令再添加一个 -C 选项,阻止 GCC 删除源文件和头文件中的注释.
# gcc -E -C test.c -o test.i
gcc -E 常用选项
选项 | 功能 |
---|---|
-D name[=definition] | 在处理源文件之前,先定义宏 name。宏 name 必须是在源文件和头文件中都没有被定义过的。将该选项搭配源代码中的#ifdef name命令使用,可以实现条件式编译。如果没有指定一个替换的值(即省略 =definition),该宏被定义为值 1。 |
-U name | 如果在命令行或 GCC 默认设置中定义过宏 name,则“取消”name 的定义。-D 和 -U 选项会依据在命令行中出现的先后顺序进行处理。 |
-include file | 如同在源代码中添加 #include “file” 一样。 |
-iquote dir | 对于以引号(#include “”)导入的头文件中,-iquote 指令可以指定该头文件的搜索路径。当 GCC 在源程序所在目录下找不到此头文件时,就会去 -iquote 指令指定的目录中查找。 |
-I dir | 同时适用于以引号 “” 和 <> 导入的头文件。当 GCC 在 -iquote 指令指定的目录下搜索头文件失败时,会再自动去 -I 指定的目录中查找。该选项在 GCC 10.1 版本中已被弃用,并建议用 -iquote 选项代替。 |
-isystem dir -idirafter dir | 都用于指定搜索头文件的目录,适用于以引号 “” 和 <> 导入的头文件。 |
对于指定 #include 搜索路径的几个选项,作用的先后顺序如下:
- 对于用 #include "" 引号形式引入的头文件,首先搜索当前程序文件所在的目录,其次再前往 -iquote 选项指定的目录中查找;
- 前往 -I 选项指定的目录中搜索;
- 前往 -isystem 选项指定的目录中搜索;
- 前往默认的系统路径下搜索;
- 前往 -idirafter 选项指定的目录中搜索。
GCC -S选项:编译非汇编文件
使用 GCC 执行预处理操作,并生成了相应的 demo.i 预处理文件。
gcc -S指令
编译是整个程序构建的核心部分,也是最复杂的部分之一。
所谓编译,简单理解就是将预处理得到的程序代码,经过一系列的词法分析、语法分析、语义分析以及优化,加工为当前机器支持的汇编代码。
通过给 gcc 指令添加 -S(注意是大写)选项,即可令 GCC 编译器仅将指定文件加工至编译阶段,并生成对应的汇编代码文件。
# gcc -S test.i
# ls
test.c test.i test.s
默认情况下,编译操作会自行新建一个文件名和指定文件相同、后缀名为 .s 的文件,并将编译的结果保存在该文件中。
gcc -S 指令添加 -o 选项,令 GCC 编译器将编译结果保存在我们指定的文件中。
gcc -S 指令可以操作预处理后的 .i 文件,也可以操作源代码文件:
- 如果操作对象为 .i 文件,则 GCC 编译器只需编译此文件;
- 如果操作对象为 .c 或者 .cpp 源代码文件,则 GCC 编译器会对其进行预处理和编译这 2 步操作。
如果想提高文件内汇编代码的可读性,可以借助 -fverbose-asm 选项,GCC 编译器会自行为汇编代码添加必要的注释。
GCC -c选项:生成目标文件
对已得到的 dmeo.s 执行汇编操作,并得到相应的目标文件。
所谓目标文件,其本质为二进制文件,但由于尚未经过链接操作,所以无法直接运行。
gcc -c指令
汇编其实就是将汇编代码转换成可以执行的机器指令。
gcc 指令添加 -c 选项(注意是小写字母 c),即可让 GCC 编译器将指定文件加工至汇编阶段,并生成相应的目标文件。
通过该指令生成了和 demo.s 同名但后缀名为 .o 的文件,这就是经过汇编操作得到的目标文件。
gcc -c 指令在添加一个 -o 选项,用于将汇编操作的结果输入到指定文件中。
-c 选项只是令 GCC 编译器将指定文件加工至汇编阶段,但不执行链接操作。这也就意味着:
- 如果指定文件为源程序文件(例如 demo.c),则 gcc -c 指令会对 demo.c 文件执行预处理、编译以及汇编这 3 步操作;
- 如果指定文件为刚刚经过预处理后的文件(例如 demo.i),则 gcc -c 指令对 demo.i 文件执行编译和汇编这 2 步操作;
- 如果指定文件为刚刚经过编译后的文件(例如 demo.s),则 gcc -c 指令只对 demo.s 文件执行汇编这 1 步操作。
GCC -o选项:指定输出文件
gcc -o选项用来指定输出文件,如果不使用 -o 选项,那么将采用默认的输出文件。
GCC -l选项:手动添加链接库
链接器把多个二进制的目标文件(object file)链接成一个单独的可执行文件。
在链接过程中,它必须把符号(变量名、函数名等一些列标识符)用对应的数据的内存地址(变量地址、函数地址等)替代,以完成程序中多个模块的外部引用。
使用 GCC 编译和链接程序时,GCC 默认会链接 libc.a 或者 libc.so,
但是对于其他的库(例如非标准库、第三方库等),就需要手动添加。
标准头文件 <math.h> 对应的数学库默认也不会被链接
GCC 的-l选项(小写的 L)可以让我们手动添加链接库。
GCC 会在-l选项后紧跟着的基本名称的基础上自动添加这些前缀、后缀。
链接其它目录中的库
GCC 会自动在标准库目录中搜索文件,例如 /usr/lib,如果想链接其它目录中的库,就得特别指明。
有三种方式可以链接在 GCC 搜索路径以外的链接库:
- 把链接库作为一般的目标文件,为 GCC 指定该链接库的完整路径与文件名。
例:
如果链接库名为 libm.a,并且位于 /usr/lib 目录,那么下面的命令会让 GCC 编译 main.c,然后将 libm.a 链接到 main.o:
# gcc main.c -o main.out /usr/lib/libm.a
-
使用-L选项,为 GCC 增加另一个搜索链接库的目录
# gcc main.c -o main.out -L/usr/lib -lm
-
把包括所需链接库的目录加到环境变量 LIBRARYPATH 中。
gcc指令一次处理多个文件
一条 gcc(g++)指令往往可以一次性处理多个文件。
# gcc -c test1.c test2.c
默认情况下会分别生成 test1.o 和 test2.o 目标文件。
注意:此方法无法使用 -o 选项
以下这些操作都可以共用一条 gcc 指令:
将多个 C(C++)源文件加工为汇编文件或者目标文件;
将多个 C(C++)源文件或者预处理文件加工为汇编文件或者目标文件;
将多个 C(C++)源文件、预处理文件或者汇编文件加工为目标文件;
同一项目中,不同的源文件、预处理文件、汇编文件以及目标文件,可以使用一条 gcc 指令,最终生成一个可执行文件。
GCC编译多文件项目
例:
一个拥有 2 个源文件的 C 语言项目:
[root@yan demo]# ls
main.c myfun.c
[root@yan demo]# cat main.c
#include <stdio.h>
int main(){
display();
return 0;
}
[root@yan demo]# cat myfun.c
#include <stdio.h>
void display(){
printf("GCC编译");
}
[root@yan demo]#
该项目中仅包含 2 个源文件,其中 myfun.c 文件用于存储一些功能函数,以方便直接在 main.c 文件中调用。
可以这样编译:
[root@yan demo]# ls
main.c myfun.c
[root@yan demo]# gcc -c myfun.c main.c
[root@yan demo]# ls
main.c main.o myfun.c myfun.o
[root@yan demo]# gcc myfun.o main.o -o main.exe
[root@yan demo]# ls
main.c main.exe main.o myfun.c myfun.o
[root@yan demo]# ./main.exe
GCC编译
gcc 指令还可以直接编译并链接它们:
[root@yan demo]# gcc myfun.c main.c -o main.exe
[root@yan demo]# ls
main.c main.exe myfun.c
[root@yan demo]# ./main.exe
GCC编译
如果一个项目中有十几个甚至几十个源文件,我们可以进入该项目目录,用 *.c 表示所有的源文件,即执行如下指令:
[root@yan demo]# ls
main.c myfun.c
[root@yan demo]# gcc *.c -o main.exe
[root@yan demo]# ls
main.c main.exe myfun.c
[root@yan demo]# ./main.exe
GCC编译
由此,大大节省了手动输入各源文件名称的时间。
静态链接库
静态链接库实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,
GCC 编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。
使用静态库文件实现程序的链接操作,既有优势也有劣势:
优势是,生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强);
劣势是,如果程序文件中多次调用库中的同一功能模块,则该模块代码势必就会被复制多次,
生成的可执行文件中会包含多段完全相同的代码,造成代码的冗余。
在 Linux 发行版系统中,静态链接库文件的后缀名通常用 .a 表示;在 Windows 系统中,静态链接库文件的后缀名为 .lib。
动态链接库
动态链接库,又称为共享链接库。和静态链接库不同,采用动态链接库实现链接操作时,程序文件中哪里需要库文件的功能模块,
GCC 编译器不会直接将该功能模块的代码拷贝到文件中,而是将功能模块的位置信息记录到文件中,直接生成可执行文件。
采用动态链接库实现程序的连接操作,其优势和劣势恰好和静态链接库相反:
优势是,由于可执行文件中记录的是功能模块的地址,真正的实现代码会在程序运行时被载入内存,
这意味着,即便功能模块被调用多次,使用的都是同一份实现代码(这也是将动态链接库称为共享链接库的原因)。
劣势是,此方式生成的可执行文件无法独立运行,必须借助相应的库文件(可移植性差)。
参考学习资料:
http://c.biancheng.net/gcc/