GCC编译过程概述

来源:公众号【很酷的程序员/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将源文件编译成最终的可执行文件,包含以下几个步骤:

  1. 预处理:将源文件处理为.ii/.i,处理各种预处理指令,如#include#ifdef#if等等,同时也会清除注释;
  2. 编译:将.ii/.i处理为.S/.asm,即机器语言的汇编文件;
  3. 汇编:将.asm/.S处理为.o,把汇编文件变成机器码;
  4. 链接:将各种依赖的静态/动态库文件、.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负责链接。

cc1collect2位于目录/usr/lib/gcc/x86_64-linux-gnu/9/下。
在不同的系统上可能会较大差异,尤其是链接的时候,很酷的童鞋们可以自行探索一番哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章