来源:公众号【很酷的程序员/RealCoolEngineer】
了解GCC如何将源文件编译为最终文件,开发者能够更加清楚如果进行调试、优化,对于理解大型程序的构建过程,使用类似CMake、Bazel等构建工具都是很有帮助的。
本文的目的在于介绍GCC的大体编译过程,使用简单的代码展示编译的完整过程,每一步的细节暂不展开。
一 GCC简介
GCC是Linux平台上最为常用的编译工具,全称GNU Compiler Collection
,即GNU编译器套件
,它包含多种语言的编译器及对应的库。
1 gcc命令
本文以C语言源文件编译进行介绍,使用的编译器工具为gcc
。
gcc
用法:
Usage: gcc [options] file...
常用参数:
参数 | 含义 |
---|---|
-o | 指定输出文件路径 |
-E | 只对源文件进行预处理,输出.i文件 |
-S | 对源文件进行预处理、编译,输出.s文件 |
-c | 对源文件进行预处理、编译、汇编,输出.o文件 |
-I | 大写的i,包含头文件路径,如 gcc -Ireal/cool/include/ |
-L | 大写的l,链接库文件路径,如 gcc -Lreal/cool/lib/ |
-l | 小写的l,链接库文件,如链接librealcool.a:gcc -lrealcool |
-fPIC | 生成位置无关代码(position-independent code) |
-Wall | 对代码所有可能有问题的地方发出警告 |
-g | 在目标文件中嵌入调试信息,便于gdb调试 |
二 gcc编译流程
考虑最简单的情况,编译一个简单的可执行文件,假设源文件demo.c
的内容如下:
// @Author: Farmer Li, 公众号: 很酷的程序员/RealCoolEngineer
// @Date: 2021-06-15
#include <stdio.h>
int func(int a, int b, int c) { return a * b + c; }
int main(void) {
int a = 10;
int b = 100;
int c = 24;
printf("%d * %d + %d = %d, Cool!\n", a, b, c, func(a, b, c));
return 0;
}
gcc
将源文件编译成最终的可执行文件,包含以下几个步骤:
-
预处理:将源文件处理为.ii/.i,处理各种预处理指令,如
#include
、#ifdef
、#if
等等,同时也会清除注释; -
编译:将
.ii/.i
处理为.S/.asm
,即机器语言的汇编文件; -
汇编:将
.asm/.S
处理为.o
,把汇编文件变成机器码; -
链接:将各种依赖的静态/动态库文件、
.o
文件、启动文件链接成最终的可执行文件或者共享库文件。
1 预处理
使用gcc -E
仅进行预处理操作:
gcc -E demo.c –o demo.i
得到的.i
文件还是文本文件,查看其内容可以看到#include <stdio.h>
和注释被处理了,其余源码在demo.i
的最后面。
2 编译
通过gcc -S
将源文件或者.i
文件编译成.s
文件,可以使用-o
指定目标名称:
gcc -S demo.c # 生成同名.s文件
# 或者
gcc -S demo.i # 生成同名.s文件
查看demo.s
文件可以看到其中有很多的汇编指令。开发者也可以自己编写汇编源代码,在编译构建完整项目的时候作为源码直接使用。
3 汇编
使用gcc -c
将源文件或者.s
文件处理成机器码,同样也可以使用-o
指定目标名称:
gcc -c demo.c # 生成同名.o文件
# 或者
gcc -c demo.s # 生成同名.o文件
在这一步,其实背后是使用汇编器as
将.s
文件处理成.o
文件的,所以下面的命令生成的.o
文件和前面两条命令完全一样:
as -o demo.o demo.s
4 链接
使用gcc
命令不带任何参数,指定源文件(.i/.s/.o/.c)则就会按需完成前面的3个步骤,并进行最终的链接,最简单的情况只需要执行命令:
gcc demo.c
就会默认将源文件编译为a.out
,当然也可以使用-o
指定输出文件的名称:
gcc demo.c -o demo
然后就可以在命令行中执行:
➜ # ./demo
10 * 100 + 24 = 1024, Cool!
-L
和-l
两个参数是给链接器使用的,用于指定目标文件,假如目标程序使用了/usr/lib/libm.a
就可以使用命令:
gcc demo.c -L/usr/lib -lm
使用-l
参数的时候,可省略库文件开头的lib
和后缀。
5 查看gcc编译的过程
可以使用-v
参数查看gcc
的详细编译过程:
gcc demo.c -v
以前面的源文件编译可执行文件为例,下面是在ubuntu 20.04
下面看到的输出(删除了大部分参数内容,只留下框架主体):
cc1 -quiet -v demo.c -Wformat -o /tmp/ccslRPiN.s
as -v --64 -o /tmp/ccaoMQtO.o /tmp/ccslRPiN.s
collect2 -L/usr/lib /tmp/ccaoMQtO.o -lgcc /usr/lib/x86_64-linux-gnu/crtn.o
可以看到,cc1
做了预处理和编译的工作,汇编由as
完成,而collect2
负责链接。
cc1
和collect2
位于目录/usr/lib/gcc/x86_64-linux-gnu/9/
下。
在不同的系统上可能会较大差异,尤其是链接的时候,很酷的童鞋们可以自行探索一番哦。