GCC编译可分为四个步骤:
预处理(Prepressing):
预处理主要处理源代码中以 " # " 开始的预编译指令,如:#include,#define等。其主要处理规则如下:
- 宏替换:将所有的 #define删除,并展开所有的宏定义。
- 条件编译:处理所有 条件预编译 指令,如#if,#ifdef,#elif,#else,#endif。
- 头文件展开:处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。该过程是递归进行的,因为被包含的文件可能还包含其它文件。
- 去注释:删除所有的注释 " // " 和 " /* */ " 。
- 添加行号和文件标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生的编译错误或警告能够显示行号。
- 保留所有的 #pragma 编译器指令,如 #pragma once 用于保证头文件只被编译一次,#pragma pack用于指定内存对齐(一般用在结构)。
预编译生成的 .i 文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到 .i 文件中。所以当我们无法判断宏定义正确或头文件包含是否正确时,可以查看预编译后文件来确定问题。
预编译步骤相当于执行如下命令:
$ gcc -E hello.c -o hello.i
//-E表示只进行预处理而不进行编译
//-o后跟生成的文件名称
//.c结尾的文件表示源程序
//.i结尾的文件表示已经过预处理的C原始程序
编译(Compilation):
编译就是把预处理生成的文件进行一系列词法分析、语法分析、语义分析、优化后生成相应的汇编代码文件,这个过程是整个程序构建的核心部分。
编译步骤相当于执行如下命令:
$ gcc -S hello.i -o hello.s
//-S表示只进行编而不进行汇编
//-o后跟生成的文件名称
//.i结尾的文件表示已经过预处理的C原始程序
//.s结尾的文件表示经过编译生成的汇编代码
汇编(Assembly):
汇编就是将汇编代码转换成机器可执行的指令,每一个汇编语句几乎都对应一条机器指令,所以汇编过程相对于编译比较简单,只需要根据汇编指令和机器指令的对照表一一进行翻译就可以了。
汇编步骤相当于执行如下命令:
$ gcc -c hello.s -o hello.o
//-c表示只进行汇编而不进行链接,生成目标文件
//-o后跟生成文件名称
//.s结尾文件表示经过编译生成的汇编代码
//.o结尾的文件表示经过汇编生成的可重定位目标二进制文件
链接(Linking):
链接就是将可重定位目标二进制文件和库文件进行链接,形成执行程序。
链接步骤相当于执行如下命令:
$ gcc hello.o -o hello
//-o后跟生成的文件名称
//.o结尾的文件表示经过汇编生成的可重定位目标二进制文件
//没有后缀的hello文件表示链接后形成的可执行程序