跳转至

GNU Make & CMake

约 1193 个字 21 行代码 预计阅读时间 4 分钟

Abstract

GNU Make 是一个用于自动化编译的工具,它可以根据文件的依赖关系自动执行编译任务。本文将介绍 GNU Make 的基本使用方法。

为啥要学 Make?

属于是为了这盘醋包了顿饺子,由于数据结构基础这门课的大作业或多或少需要用到多文件编译,我干脆就希望使用 Make 进行自动化编译,而且在系统课上接触了 Makefile 的编写,所以就有了这篇文章。

Part 1 别急,先看看 gcc 编译!

gcc 的编译过程可以简要分为四个阶段:预处理编译汇编链接

gcc 编译工具链是以 gcc 编译器为核心的一整套工具,主要包含以下三部分内容:

  • gcc-core:亦即 gcc 编译器,用于完成预处理过程和编译过程,将 C 代码转换成汇编代码;
  • Binutils:包含了除了 gcc 编译器以外的一系列小工具,比如汇编器 as、连接器 ld、目标文件格式查看器 readelf 等;
  • glibc:GNU C Library,是 GNU 组织为了 GNU 系统以及 Linux 系统编写的 C 语言标准库。

1.1 预处理/Pre-Processing

预处理阶段的主要任务是处理源文件以 # 开头的预处理指令,比如 #include#define 等。这里主要是将 #include 的一些头文件宏定义进行展开,生成一个 .i 文件。

预处理过程输入的是 C 的源文件,输出的是一个中间/预加载文件,这个文件还是 C 代码。此阶段使用 gcc 参数 -E,同时参数 -o 指定了最后输出文件的名字,下面的例子就将 main.c 文件经过预处理生成 main.i 文件

gcc -E main.c -o main.i

1.2 编译/Compiling

编译过程使用 gcc 编译器将预处理后的 .i 文件通过编译转换为汇编语言,生成一个 .s 文件。这是 gcc 编译器完成的工作,在这部分过程之中,gcc 编译器会检查各个源文件的语法,即使我们调用了一个没有定义的函数,也不会报错,

编译过程输入的是一个中间/预加载文件,输出的是一个汇编文件,当然,直接以 C 文件作为输入进行编译也是可以的。此阶段使用 gcc 参数 -S,具体例子如下:

gcc -S main.i -o main.s
gcc -S main.c -o main.s

1.3 汇编/Assembling

汇编阶段的主要任务是将汇编语言文件经过汇编,生成目标文件 .o 文件,每一个源文件都对应一个目标文件。即把汇编语言的代码转换成机器码,这是 as 汇编器完成的工作。

汇编过程输入的是汇编文件,输出 .o 后缀的目标文件,gcc 的参数 -c 表示只编译源文件但不链接,当然,我们也可以直接输入 C 源文件,就直接包含了前面两个过程。

gcc -c main.s -o main.o
gcc -c main.c -o main.o

Linux下生成的 .o 目标文件、.so 动态库文件以及下一小节链接阶段生成最终的可执行文件都是 elf 格式的, 可以使用 readelf 工具来查看它们的内容。

从 readelf 的工具输出的信息,可以了解到目标文件包含ELF头、程序头、节等内容,对于 .o 目标文件或 .so 库文件,编译器在链接阶段利用这些信息把多个文件组织起来,对于可执行文件,系统在运行时根据这些信息加载程序运行。

1.4 链接/Linking

最后将每个源文件对应的 .o 文件链接起来,就生成了一个可执行程序文件,这是这是链接xx器ld完成的工作。

例如一个工程里包含了 A 和 B 两个代码文件,在链接阶段,链接过程需要把 A 和 B 之间的函数调用关系理顺,也就是说要告诉 A 在哪里能够调用到 fun 函数,建立映射关系,所以称之为链接。若链接过程中找不到 fun 函数的具体定义,则会链接报错。

链接分为两种:

  • 动态链接:gcc 编译时的默认选项。动态是指在应用程序运行时才去加载外部的代码库,不同的程序可以共用代码库。所以动态链接生成的程序比较小,占用较少的内存。
  • 静态链接:链接时使用选项 --static,它在编译阶段就会把所有用到的库打包到自己的可执行程序中。所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大。
gcc main.o -o main
gcc main.c -o main --static