1 简介
GCC(GNU Compiler Collection,GNU编译器套件)是由GNU开发的编程语言译器,GNU工具链也包含了编译器、汇编器和连接器。一个C/C++文件要经过预处理(Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)才能变成可执行文件,过程如下图所示:
- 预处理
C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到.i
文件中等待进一步处理。 - 编译
编译就是把C/C++代码(比如上述的.i
文件)“翻译”成汇编代码。 - 汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。(“反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。) - 链接
链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件。
GNU工具链的语法与ARM汇编语法有些不同,这些不同点包括变量定义、编译指示字、以及the like。
预处理不做语法检查,只是将include
包含的文件插入原文件中、将宏定义展开、根据条件编译命令,如源文件hello.c
预处理为hello.i
:
#include <stdio.h>
#define MAX 20
#define MIN 10
// #define _DEBUG
#define SetBit(x) (1<<x)
int main(int argc, char* argv[])
{
printf("Hello World \n");
printf("MAX = %d,MIN = %d,MAX + MIN = %d\n",MAX,MIN,MAX + MIN);
printf("SetBit(5) = %d,SetBit(6) = %d\n",SetBit(5),SetBit(6));
#ifdef _DEBUG
printf("SetBit( SetBit(2) ) = %d\n",SetBit( SetBit(2) ));
#endif
return 0;
}
#include <stdio.h>
展开代码太多不做展示,上面的宏而预处理命令展开如下:
MAX
、 MIN
、SetBit(x)
进行了展开,条件处理了#ifdef _DEBUG
。
因为不做语法检查,添加非法代码也能正常预编译:
在编译阶段才会做语法检查:
2 基本语法与命令
gcc命令区分大小写。
2.1 使用方法
gcc [选项] 文件名
gcc常用选项:
选项 | 功能 |
---|---|
-v | 查看gcc编译器的版本,显示gcc执行时的详细过程 |
-o <file> | 指定输出文件名为file,这个名称不能跟源文件名同名 |
-E | 只预处理,不会编译、汇编、链接 |
-S | 只编译,不会汇编、链接 |
-c | 编译和汇编,不会链接 |
-I <头文件目录> | 指令头文件(.h)目录(不指定优先从系统目录寻找) |
-L <库目录> | 指定依赖的库目录(so、或lib、dll) |
Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static
选项,强制使用静态链接库。如:
gcc -L /usr/dev/test/lib -static -o hello hello.o
2.2 常用命令
hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello
详细的每一步命令如下:
gcc -E -o hello.i hello.c // 预处理
gcc -S -o hello.s hello.i // 编译
gcc -c -o hello.o hello.s // 汇编
gcc -o hello hello.o // 链接
gcc会对.c文件默认进行预处理操作,因此可使用-c
再来指明了编译、汇编,从而得到.o文件,再将.o文件进行链接,得到可执行应用程序:
// 方法一
gcc -c -o hello.o hello.c // 先编译(做了前三步,但不链接)
gcc -o hello hello.o // 再链接
// 方法二
gcc -o hello hello.c // 编译+链接 (多个文件每次都会全部编译,效率低下)
对于多个源文件,使用gcc -o <file> file.c
命令简单,如:
gcc -o hello a.c b.c c.c
同时编译三个源文件,但是效率低下,某个文件修改就会全部重新编译,所以一般使用方法一:
gcc -o a.o a.c
gcc -o b.o b.c
gcc -o c.o c.c
gcc -o hello a.o b.o c.o
3 Makefile的引入及规则
3.1 基本规则
对于MDK等IDE首次编译工程时会全部编译,后面点击单编译只会编译修改过的文件,该功能Makefile+gcc也可以做到。
makefie最基本的语法是规则,规则:
目标 : 依赖1 依赖2 ...
[TAB]命令
当“依赖”比“目标”新(依据文件修改时间),执行它们下面的命令。我们要把上面三个命令写成makefile规则,如下:
test :a.o b.o // test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比test新的时候,就需要执行下面的命令,重新生成test可执行程序。
gcc -o test a.o b.o
a.o : a.c // a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
b.o : b.c // b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o
gcc -c -o b.o b.c
3.2 makefile实验
- a.c
#include <stdio.h>
int main()
{
func_b();
return 0;
}
- b.c
#include <stdio.h>
void func_b()
{
printf("This is B\n");
}
- makefile
test:a.o b.o
gcc -o test a.o b.o
a.o : a.c
gcc -c -o a.o a.c
b.o : b.c
gcc -c -o b.o b.c
输入make
执行makefile:
执行了三条命令:
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o
再次执行make
:
gcc -o test a.o b.o // 按理说会显示make: `test' is up to date.
修改a.c后:
gcc -c -o a.o a.c
gcc -o test a.o b.o
END