GCC 编译全过程
GCC 编译全过程
本文仅总结了大部分常用的 GCC 命令,共大家学习参考
GCC 简介
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
简单编译
实例程序 main.c 如下:
#include <stdio.h>
int main(void)
{
printf("Hello ppqppl! \n");
return 0;
}
这个程序,一步到位的编译命令如下:
gcc main.c -o main
实际上,上述的编译步骤分为四个阶段:
1.预处理(也叫预编译,Preprocessing)
2.编译(Compilation)
3.汇编(Assenbly)
4.连接(Linking)
预处理
主要包括一下过程:
1.将所有的 #define 删除,并展开所有宏定义,处理所有的条件预编译指令,例如 #if #ifdef 等
2.处理 #include 预编译命令,将被包含的文件插入到该预编译指令的位置
3.删除所有注释
4.添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告的行号
5.保留所有的 #pragma 编译器指令,后迅速编译过程需要使用
预处理命令如下:
gcc -E main.c -o main.i
#或
gcc -E main.c
可以直接输出预处理后存放在 main.c 中的代码,gcc 的 -E 选项,可以让编译器在预处理后停止,-o 表示输出编译后的文件。并输出预处理结果。
编译为汇编代码
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码
GCC 命令如下:
gcc -S main.i -o main.s
gcc 的 -S 选项,表示在程序编译期间,在生成汇编代码后,停止
汇编
汇编过程调用对汇编代码进行处理,生成机器可以识别的指令,由于每一个汇编语句几乎都对应一条处理器指令,所以,汇编想对于编译过程简单一些,调用 Binutils 中的汇编器 as 根据汇编指令和处理器指令的对照表一一翻译即可
当程序由多个源代码文件构成时,每个文件都要先完成汇编,生成 .o 目标文件之后,才能进行链接
对于上一小节中生成的汇编代码文件 main.s,gas 汇编器负责将其编译为目标文件
gcc -c main.s -o main.o
# 或者直接使用 as 进行汇编
as -c main.s -o main.o
注意: hello.o目标文件为 ELF( ELF ) 格式的可重定向文件
关于 ELF 文件的介绍,请看:GCC 一步到位的 ELF 部分
连接
链接分为:动态链接、静态链接
静态链接
在编译阶段直接把静态库加入到可执行文件中,可执行文件较大。连接器将函数代码从所在地拷贝到最终执行程序中
动态链接
链接阶段,仅仅只加入一些描述信息,而程序执行是从系统中制定位置把相应动态库加载到内存中
注意: 关于动态库与静态库的进一步内容,可以看我的另一篇文章:GCC 入门之 静态库 与 动态库
连接过程:
为创建可执行文件,连接器必须要完成的任务是:符号解析、重定位
符号解析:把目标文件中符号的定义和引用联系起来
重定位:把符号定义和内存地址对应起来然后修改所有对符号的作用
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的 main.o,将其与C标准输入输出库进行连接,最终生成程序 main
gcc main.o -o main
在命令行窗口,可以直接执行 ./test,运行结果如下:
多个程序文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理这些编译单元。假设有一个由 main.c 和 sub1.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 main,可以使用下面这条命令:
gcc main.c sub1.c -o main
如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:
gcc -c main.c -o main.o
gcc -c sub1.c -o sub1.o
gcc main.o sub1.o -o main
检错
gcc -pedantic main.c -o main
-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助 Linux 程序员离这个目标越来越近。
或者换句话说,-pedantic 选项能够帮助程序员发现一些不符合 ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。
除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以 -W 开头,其中最有价值的当数 -Wall 了,使用它能够使 GCC 产生尽可能多的警告信息。
gcc -Wall main.c -o main
GCC 给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的 Linux 程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上 -Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -Werror main.c -o main