主要内容 #
- gcc简介
- gcc编译
- gcc的使用
1. gcc简介 #
GCC(GNU Compiler Collection)是由 GNU 开发的编程语言编译器。 GCC最初代表“GNU C Compiler”,当时只支持C语言。 后来又扩展能够支持更多编程语言,包括 C++、Fortran 和 Java 等。 因此,GCC也被重新定义为“GNU Compiler Collection”,成为历史上最优秀的编译器, 其执行效率与一般的编译器相比平均效率要高 20%~30%。
GCC 编译工具链在编译一个C源文件时需要经过以下 4 步:
预处理:为把头文件的代码、宏之类的内容转换成生成的.i文件,还是C代码。
编译:把预处理后的.i文件通过编译成.s文件,汇编语言。
汇编:将汇编语言文件生成目标文件.o文件,机器码。
链接:将每个源文件对应的.o文件链接起来,就生成一个可执行程序文件。
2. gcc编译 #
(1)预处理阶段
预处理过程中,对源代码文件中的文件包含 (include)、 预编译语句 (如宏定义define等)进行展开,生成 .i 文件。 可理解为把头文件的代码、宏之类的内容转换成更纯粹的C代码,不过生成的文件以.i为后缀。
使用GCC的参数 “-E”,可以让编译器生成 .i 文件,参数 “-o”,可以指定输出文件的名字。
具体命令如下:
# 预处理,可理解为把头文件的代码汇总成C代码,把*.c转换得到*.i文件 gcc –E hello.c –o hello.i
相当于它把原C代码中包含的头文件中引用的内容汇总到一处, 如果原C代码有宏定义,它把宏定义展开成具体的内容。
(2)编译阶段
把预处理后的.i文件通过编译成为汇编语言,生成.s文件,即把代码从C语言转换成汇编语言,这是GCC编译器完成的工作。在这个过程,GCC会检查各个源文件的语法,即使我们调用了一个没有定义的函数,也不会报错。
GCC可以使用-S选项,让编译程序生成汇编语言的代码文件(.s后缀)。
具体命令如下:
# 编译,可理解为把C代码转换为汇编代码,把*.i转换得到*.s文件 gcc –S hello.i –o hello.s # 也可以直接以C文件作为输入进行编译,与上面的命令是等价的 gcc –S hello.c –o hello.s
汇编语言是跟平台相关的,若GCC目标平台是x86,所以此处生成的汇编文件是x86的汇编代码。
(3)汇编阶段
将汇编语言文件经过汇编,生成目标文件.o文件,每一个源文件都对应一个目标文件。即把汇编语言的代码转换成机器码,这是as汇编器完成的工作。
GCC的参数“c”表示只编译(compile)源文件但不链接,会将源程序编译成目标文件(.o后缀)。计算机只认识0或者1,不懂得C语言,也不懂得汇编语言,经过编译汇编之后,生成的目标文件包含着机器代码,这部分代码就可以直接被计算机执行。一般情况下,可以直接使用参数“c”,跳过上述的两个过程,具体命令 如下:
#汇编,可理解为把汇编代码转换为机器码,把*.s转换得到*.o,即目标文件 gcc –c hello.s –o hello.o # 也可以直接以C文件作为输入进行汇编,与上面的命令是等价的 gcc –c hello.c –o hello.o
Linux下生成的 *.o目标文件、*so动态库文件以及下一小节链接阶段生成最终的可执行文件都是elf格式的, 可以使用“readelf”工具来查看它们的内容。
(4)链接阶段
最后将每个源文件对应的目标.o文件链接起来,就生成一个可执行程序文件,这是链接器ld完成的工作。
例如一个工程里包含了A和B两个代码文件,在链接阶段, 链接过程需要把A和B之间的函数调用关系理顺,也就是说要告诉A在哪里能够调用到fun函数, 建立映射关系,所以称之为链接。若链接过程中找不到fun函数的具体定义,则会链接报错。
虽然本示例只有一个hello.c文件,但它调用了C标准代码库的printf函数, 所以链接器会把它和printf函数链接起来,生成最终的可执行文件。
链接分为两种:
动态链接:GCC编译时的默认选项。动态是指在应用程序运行时才去加载外部的代码库,不同的程序可以共用代码库。 所以动态链接生成的程序比较小,占用较少的内存。
静态链接:链接时使用选项 “–static”,它在编译阶段就会把所有用到的库打包到自己的可执行程序中。 所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大。
执行如下命令体验静态链接与动态链接的区别:
# 在hello.o所在的目录执行如下命令 # 动态链接,生成名为hello的可执行文件 gcc hello.o –o hello # 也可以直接使用C文件一步生成,与上面的命令等价 gcc hello.c -o hello # 静态链接,使用--static参数,生成名为hello_static的可执行文件 gcc hello.o –o hello_static --static # 也可以直接使用C文件一步生成,与上面的命令等价 gcc hello.c -o hello_static --static
gcc的使用 #
GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。
如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编 译生成一个计算阶乘的程序。
int factorial (int n) { if (n <= 1) return 1; else return factorial (n - 1) * n; }
#include <stdio.h> #include <unistd.h< int factorial (int n); int main (int argc, char **argv) { int n; if (argc < 2) { printf ("Usage: %s n\n", argv [0]); return -1; } else { n = atoi (argv[1]); printf ("Factorial of %d is %d.\n", n, factorial (n)); } return 0; }
利用如下的命令可编译生成可执行文件,并执行程序:
$ gcc -o factorial main.c factorial.c $ ./factorial 5 Factorial of 5 is 120.
GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.cpp):
#include <iostream> void main (void) { cout << "Hello, world!" << endl; }
则可以如下调用 g++ 命令编译、连接并生成可执行文件:
$ g++ -o hello hello.c $ ./hello Hello, world!