GCC
GNU 计划,最终的目标是打造一套完全自由、开源的操作系统。
早期 GCC 的全拼为 GNU C Compiler,即 GUN 计划诞生的 C 语言编译器,显然最初 GCC 的定位确实只用于编译 C 语言。
GCC的功能得到了很大的扩展,它不仅可以用来编译C语言程序,还可以处理C++、GO等多种语言。被重新定义为 GNU Compiler Collection,即 GNU 编译器套件。
安装GCC编译器
1 | yum -y install gcc |
以源码的方式安装
1
2yum install -y glibc-static libstdc++-static # 所需的静态链接库
yum install -y gcc gcc-c++安装包
gcc-10.1.0.tar.gz
Windows平台安装
GCC 移植版主要有 2 种,分别为 MinGW 和 Cygwin。
- MinGW 侧重于服务 Windows 用户可以使用 GCC 编译环境,直接生成可运行 Windows 平台上的可执行程序,相比后者体积更小,使用更方便。
- Cygwin 则可以提供一个完整的 Linux 环境,借助它不仅可以在 Windows 平台上使用 GCC 编译器,理论上可以运行 Linux 平台上所有的程序。
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++ 类和函数 |
C程序的编译过程
预处理
处理那些源文件和头文件中以#
开头的命令。
规则:
- 将所有的
#define
删除,并展开所有的宏定义。 - 处理所有条件编译命令,比如 #if、#ifdef、#elif、#else、#endif 等。
- 处理
#include
命令,将被包含文件的内容插入到该命令所在的位置,这与复制粘贴的效果一样。注意,这个过程是递归进行的,也就是说被包含的文件可能还会包含其他的文件。 - 删除所有的注释
//
和/* ... */
。 - 添加行号和文件名标识,便于在调试和出错时给出具体的代码位置。
- 保留所有的
#pragma
命令,因为编译器需要使用它们。
预处理的结果是生成.i
文件。
命令:
1 | g++ -E hello.cpp -o hello.i |
-E
表示只进行预编译。
编译
进行一些列的词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。
编译的结果是生成.s
文件。
命令:
1 | g++ -S hello.cpp -o hello.s |
汇编
将汇编代码转换成可以执行的机器指令。
汇编的结果是产生目标文件,在 GCC 下的后缀为.o
,在 Visual Studio 下的后缀为.obj
。
链接
目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,程序不能执行。
链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。
GCC编译C/C++程序
1 | g++ hello.cpp |
GCC 编译器会在当前目录下生成一个名为 a.out 的可执行文件。
gcc/g++指令选项 | 功 能 |
---|---|
-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 编译器仅对源代码做预处理操作。
默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。
和 -o 选项连用,将结果导入到指令的文件中。
1 | g++ -E hello.cpp -o hello.i |
为 gcc 指令再添加一个 -C 选项,阻止 GCC 删除源文件和头文件中的注释。
1 | gcc -E -C demo.c -o demo.i |
GCC -S选项:编译非汇编文件
编译是整个程序构建的核心部分,也是最复杂的部分之一。
编译,简单理解就是将预处理得到的程序代码,经过一系列的词法分析、语法分析、语义分析以及优化,加工为当前机器支持的汇编代码。
默认情况下,编译操作会自行新建一个文件名和指定文件相同、后缀名为 .s 的文件,并将编译的结果保存在该文件中。
为 gcc -S 指令添加 -o 选项,令 GCC 编译器将编译结果保存在我们指定的文件中。
1 | gcc -S demo.i -o test.i |
最终生成的 .s 汇编文件。
GCC -c选项:生成目标文件
汇编其实就是将汇编代码转换成可以执行的机器指令。
为 gcc 指令添加 -c 选项,即可让 GCC 编译器将指定文件加工至汇编阶段,并生成相应的目标文件。
1 | gcc -c demo.s |
生成了和 demo.s 同名但后缀名为 .o 的文件,这就是经过汇编操作得到的目标文件。
可以为 gcc -c 指令在添加一个 -o 选项,用于将汇编操作的结果输入到指定文件中。
1 | gcc -c demo.s -o test.o |
GCC -l选项:手动添加链接库
链接器把多个二进制的目标文件(object file)链接成一个单独的可执行文件。
在链接过程中,它必须把符号(变量名、函数名等一些列标识符)用对应的数据的内存地址(变量地址、函数地址等)替代,以完成程序中多个模块的外部引用。
当把程序链接到一个链接库时,只会链接程序所用到的函数的目标文件。
标准库的大部分函数通常放在文件 libc.a 中,或者放在用于共享的动态链接文件 libc.so 中。
链接库一般位于 /lib/ 或 /usr/lib/,或者位于 GCC 默认搜索的其他目录。
GCC 默认会链接 libc.a 或者 libc.so,但是对于其他的库(例如非标准库、第三方库等),就需要手动添加。
1 | gcc main.c -o main.out -lm |
有三种方式可以链接在 GCC 搜索路径以外的链接库:
链接库作为一般的目标文件,为 GCC 指定该链接库的完整路径与文件名。
1
gcc main.c -o main.out /usr/lib/libm.a
用
-L
选项,为 GCC 增加另一个搜索链接库的目录:1
gcc main.c -o main.out -L/usr/lib -lm
包括所需链接库的目录加到环境变量 LIBRARYPATH 中。
gcc指令一次处理多个文件
1 | gcc -c demo1.c demo2.c |
GCC编译多文件项目
1 | gcc -c myfun.c main.c |
gcc 指令还可以直接编译并链接它们
1 | gcc myfun.c main.c -o main.exe |
如果一个项目中有十几个甚至几十个源文件,用 *.c 表示所有的源文件。
1 | gcc *.c -o main.exe |
GCC使用静态链接库和动态链接库
库文件中每个目标文件存储的代码,并非完整的程序,而是一个个实用的功能模块。
C++ 库文件不仅提供有使用的函数,还有大量事先设计好的类(如 string 字符串类)。
头文件和库文件并不是一码事,它们最大的区别在于:头文件只存储变量、函数或者类等这些功能模块的声明部分,库文件才负责存储各模块具体的实现部分。
所有的库文件都提供有相应的头文件作为调用它的接口。
库文件是无法直接使用的,只能通过头文件间接调用。
库文件只是一个统称,代指的是一类压缩包,它们都包含有功能实用的目标文件。
库文件用于程序的链接阶段。
采用静态链接方式实现链接操作的库文件,称为静态链接库;采用动态链接方式实现链接操作的库文件,称为动态链接库。
静态链接库
程序文件中哪里用到了库文件中的功能模块,GCC 编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。
优势劣势:
- 优势是,生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强);
- 劣势是,如果程序文件中多次调用库中的同一功能模块,则该模块代码势必就会被复制多次,生成的可执行文件中会包含多段完全相同的代码,造成代码的冗余。
动态链接库
程序文件中哪里需要库文件的功能模块,GCC 编译器不会直接将该功能模块的代码拷贝到文件中,而是将功能模块的位置信息记录到文件中,直接生成可执行文件。
采用动态链接库生成的可执行文件运行时,GCC 编译器会将对应的动态链接库一同加载在内存中,由于可执行文件中事先记录了所需功能模块的位置信息,所以在现有动态链接库的支持下,也可以成功运行。
优势劣势:
- 真正的实现代码会在程序运行时被载入内存,这意味着,即便功能模块被调用多次,使用的都是同一份实现代码。
- 此方式生成的可执行文件无法独立运行,必须借助相应的库文件(可移植性差)。
动态链接库生成的可执行文件的体积更小,因为其内部不会被复制一堆冗余的代码。
GCC 编译器生成可执行文件时,默认情况下会优先使用动态链接库实现链接操作,除非当前系统环境中没有程序文件所需要的动态链接库,GCC 编译器才会选择相应的静态链接库。
静态链接库和动态链接库通常存放在 /usr/bin 或者 /bin 目录下。