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
文件:
1.2 编译/Compiling¶
编译过程使用 gcc 编译器将预处理后的 .i
文件通过编译转换为汇编语言,生成一个 .s
文件。这是 gcc 编译器完成的工作,在这部分过程之中,gcc 编译器会检查各个源文件的语法,即使我们调用了一个没有定义的函数,也不会报错,
编译过程输入的是一个中间/预加载文件,输出的是一个汇编文件,当然,直接以 C 文件作为输入进行编译也是可以的。此阶段使用 gcc 参数 -S
,具体例子如下:
1.3 汇编/Assembling¶
汇编阶段的主要任务是将汇编语言文件经过汇编,生成目标文件 .o
文件,每一个源文件都对应一个目标文件。即把汇编语言的代码转换成机器码,这是 as 汇编器完成的工作。
汇编过程输入的是汇编文件,输出 .o
后缀的目标文件,gcc 的参数 -c
表示只编译源文件但不链接,当然,我们也可以直接输入 C 源文件,就直接包含了前面两个过程。
Linux下生成的 .o
目标文件、.so
动态库文件以及下一小节链接阶段生成最终的可执行文件都是 elf 格式的, 可以使用 readelf 工具来查看它们的内容。
从 readelf 的工具输出的信息,可以了解到目标文件包含ELF头、程序头、节等内容,对于 .o
目标文件或 .so
库文件,编译器在链接阶段利用这些信息把多个文件组织起来,对于可执行文件,系统在运行时根据这些信息加载程序运行。
1.4 链接/Linking¶
最后将每个源文件对应的 .o
文件链接起来,就生成了一个可执行程序文件,这是这是链接xx器ld完成的工作。
例如一个工程里包含了 A 和 B 两个代码文件,在链接阶段,链接过程需要把 A 和 B 之间的函数调用关系理顺,也就是说要告诉 A 在哪里能够调用到 fun
函数,建立映射关系,所以称之为链接。若链接过程中找不到 fun
函数的具体定义,则会链接报错。
链接分为两种:
- 动态链接:gcc 编译时的默认选项。动态是指在应用程序运行时才去加载外部的代码库,不同的程序可以共用代码库。所以动态链接生成的程序比较小,占用较少的内存。
- 静态链接:链接时使用选项
--static
,它在编译阶段就会把所有用到的库打包到自己的可执行程序中。所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大。