目录
前言:
GCC编译过程:
预处理:
编译阶段:
汇编:
链接阶段
GCC的常见使用
前言:
什么是GCC:
gcc的全称是GNU Compiler Collection,它是一个能够编译多种语言的编译器。最开始gcc是作为C语言的编译器(GNU C Compiler),现在除了c语言,还支持C++、java、Pascal等语言。gcc支持多种硬件平台。
特点:
- gcc是一个可移植的编译器,支持多种硬件平台。例如ARM、X86等等。
- gcc不仅是个本地编译器,它还能跨平台交叉编译。所谓的本地编译器,是指编译出来的程序只能够在本地环境进行运行。而gcc编译出来的程序能够在其他平台进行运行。例如嵌入式程序可在x86上编译,然后在arm上运行。
- gcc有多种语言前端,用于解析不同的语言。
- gcc是按模块化设计的,可以加入新语言和新CPU架构的支持。
- gcc是自由软件。任何人都可以使用或更改这个软件。
源文件需要经过编译才能够生成可执行文件,在Windows下进行开发的时候,只需要几个按键就可以编译,集成的开发环境(V Studio,keil,clion等)已经将各种编译工具封装好了,linux中虽然有一些集成好的编译器,但是对于嵌入式软件开发的时候,通常没有屏幕,只能通过指令进行程序的编写和调试。但是要编译出来的程序可以在ARM上运行,就必须使用交叉编译器xxx-gcc等。
GCC编译过程:
一个c/c++文件要经过四个过程才能变成可执行文件。
- 预编译(Pre-Processing)
- 编译(Compiling)
- 汇编(Assembling)
- 链接(Linking)
预处理:
预处理过程中,是对源代码文件中的包含(include)、预编译语句(以#开头的命令等)进行展开生成.i文件。
使用GCC的参数 “-E”,可以让编译器生成 .i 文件,参数 “-o”,可以指定输出文件的名字。
# 预处理,可理解为把头文件的代码汇总成C代码,把*.c转换得到*.i文件 gcc –E test1.c –o test1.i
预处理就是将要包含“#include”的文件插入到原文件中,将宏定义展开,根据条件编译命令选择要使用的代码,最后将这些东西输入到“.i”文件中等待进一步处理
编译阶段:
gcc调用不同语言的编译器,例如c语言调用编译器ccl。gcc实际上是个工具链,在编译程序的过程中调用不同的工具。而在编译阶段就是通过调用的工具将预处理后的.i文件编译成汇编语言,生成.s文件,就是把代码从C语言转为汇编语言。在这个过程,GCC会检查各个源文件的语法。
GCC可以使用-S选项,让编译程序生成汇编语言的代码文件(.s后缀)。
# 编译,可理解为把C代码转换为汇编代码,把*.i转换得到*.s文件 gcc –S hello.i –o hello.s # 也可以直接以C文件作为输入进行编译,与上面的命令是等价的 gcc –S hello.c –o hello.s
了解过汇编语言的应该知道,对于不同的芯片会由不同的汇编指令集,这个是没法通用的。所以这是为什么要使用交叉编译器的一个主要原因。
汇编:
汇编阶段,gcc调用汇编器进行汇编。将汇编语言的.s文件经过汇编生成.o文件,每一个源文件都对应一个目标文件。把汇编语言转换成机器码。
# 汇编,可理解为把汇编代码转换为机器码,把*.s转换得到*.o,即目标文件 gcc –c hello.s –o hello.o # 也可以直接以C文件作为输入进行汇编,与上面的命令是等价的 gcc –c hello.c –o hello.o
双击.o文件是打不开的,使用vim打开也是一堆乱码,这是因为Linux下生成的.o目标文件、.so动态库文件以及可执行文件都是elf格式的。可以使用“readelf”工具来查看内容。
从 readelf 的工具输出的信息,可以了解到目标文件包含ELF头、程序头、节等内容, 对于*.o目标文件或*.so库文件,编译器在链接阶段利用这些信息把多个文件组织起来。
链接阶段
最后将每个源文件对应的目标文件.o链接起来,就生成一个成执行文件了。
例如一个工程里包含了A和B两个代码文件,在链接阶段, 链接过程需要把A和B之间的函数调用关系理顺,也就是说要告诉A在哪里能够调用到fun函数, 建立映射关系,所以称之为链接。若链接过程中找不到fun函数的具体定义,则会链接报错。
虽然文章的test1例子中只有一个.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
从图中可以看到,使用动态链接生成的hello程序才16.6KB, 而使用静态链接生成的hello_static程序则高达871.9KB。
在Ubuntu下,可以使用 ldd 工具查看动态文件的库依赖,尝试执行如下命令:
但是静态链接生成的可执行文件没有依赖外部库文件。
GCC的常见使用
//gcc使用示例
gcc hello.c // 输出一个名为 a.out的可执行程序,然后可以执行./a.out
gcc -o hello hello.c // 输出名为 hello的可执行程序,然后可以执行./hello
gcc -o hello hello.c -static // 静态链接
gcc -c -o hello.o hello.c // 先编译(不链接)
gcc -o hello hello.o // 再链接
执行“gcc -o test1 test1.c -v”的时候可以看到这写步骤
常用编译选项:
多文件编译
//一次性编译
gcc -o main hello.c main.c //生成可执行文件main
//独立编译
gcc -Wall -c-o main.o main.c
gcc -Wall -c -o hello.o hello.c
gcc -Wall -o main main.o hello.o
制作动态库:
静态库:
文章参考:
GCC基本使用 - 知乎 (zhihu.com)
Linux编译工具:gcc入门 - melonstreet - 博客园 (cnblogs.com)