skip to content
Astro Cactus

C Programming Compilation, GCC and Make

/ 8 min read

Updated:

Compilation Process

compilation overview
  • 淡蓝色方块是你写的代码
  • 淡绿色方块是是C的内置文件
  • 黄色云朵是编译流程的阶段,每一个阶段对应一个程序,gcc会自动调用这些程序
  • 白色方块是每一个阶段的输出,它们也是下一个阶段的输入
  • 深蓝色方块是最终的产物,一个可执行文件

比较有意思的一点是,虽然Compiler只是整个过程的一个程序,Compilation只是整个过程的一个阶段,但是我们还是把这两个名词单独拎出来代表所有的程序和一整个流程。下面是各个程序或阶段的简介…

C Preprocessing

预处理阶段会把你在代码中#include的头文件和宏(Macro)展开。 关于头文件:

  • 无论是内置的还是我们自己开发的,一般包含函数原型(function prototypes)、宏定义(macro definition)、类型声明(type declarations)
  • 函数原型有一个很重要的作用,编译器可以在不需要知道整个函数的定义的情况下走到Assembling那一步,输出目标文件(.o后缀的文件)。

Compiler

  • 编译器读取了经过预处理的源码后,处理并输出汇编代码。
  • 编译过程中的语法检查就是由编译器程序来做的。
  • 编译器程序有一个优化的选项,有好几个Level,对应不同的优化配置,常用的比方说-O3 Level。一般的做法是,在程序的测试和debug阶段不开启优化选项,最终完成再才编译优化来走一遍testing cases。

Assembling

  • 汇编阶段的输入是汇编文件,输出是目标文件,也就是把汇编指令转为01的表示。
  • 这个阶段有可能报错,比如你使用asm语句往C代码里面塞汇编指令,结果汇编过程发现目标平台不支持这个指令。
  • 前面提到,目标文件可以引用未在源文件定义的函数的原型。当我们在开发一个模块时,可以交付一个目标文件和对应的头文件,让调用方自行使用。

Linking

链接过程就是把多个目标文件(.o)和一些内置的libraries、以及程序运行的启动代码结合成一个可执行文件。前面提到的函数原型的引用,就是在这个阶段做进一步处理的,链接器会检索原型对应的函数定义。

Make, Makefile and gcc

Make是一个自动化构建工具,具备依赖关系管理、自动化执行编译的功能。 Makefile是告诉Make工具如何构建项目的描述文件, gcc是一个编译器,包含了上面提到的Compilation Process的所有程序。

它们怎么组合使用?举一个例子:

myProgram: oneFile.o anotherFile.o
gcc -o myProgram oneFile.o anotherFile.o
oneFile.o: oneFile.c oneHeader.h someHeader.h
gcc -std=gnu99 -pedantic -Wall -c oneFile.c
anotherFile.o: anotherFile.c anotherHeader.h someHeader.h
gcc -std=gnu99 -pedantic -Wall -c anotherFile.c

在命令行工具中执行make myProgram,Make会加载Makefile文件,读取对应的target(myProgram),寻找它的dependencies(oneFile.o, anotherFile.o),解析dependencies是否有其他的dependencies。等到所有的依赖关系都解决了,Make就会自下而上调用这份规则中的命令来编译整个项目,执行的命令如下:

gcc -std=gnu99 -pedantic -Wall -c oneFile.c
gcc -std=gnu99 -pedantic -Wall -c anotherFile.c
gcc -o myProgram oneFile.o anotherFile.o

Make还会检查依赖文件是否为最新的,检查的过程与解析依赖的过程一样,是一个自顶向下的过程。例如,第一次构建完成后,我修改了oneFile.c,没有修改其他文件,那么具体的执行过程就会变成:

gcc -std=gnu99 -pedantic -Wall -c anotherFile.c
gcc -o myProgram oneFile.o anotherFile.o

常用的Make & Makefile配置

Variables

Makefile支持声明变量,减少重复。举个例子,注意CFLAGS变量的声明和使用:

CFLAGS=-std=gnu99 -pedantic -Wall
myProgram: oneFile.o anotherFile.o
gcc -o myProgram oneFile.o anotherFile.o
oneFile.o: oneFile.c oneHeader.h someHeader.h
gcc $(CFLAGS) -c oneFile.c
anotherFile.o: anotherFile.c anotherHeader.h someHeader.h
gcc $(CFLAGS) -c anotherFile.c

clean

有时候我们需要清理所有的构建产物(包括中间文件和最终的可执行文件),可以在Makefile中定义:

.PHONY: clean
clean:
rm -f myProgram *.o

然后执行make clean就会调用对应的命令rm -f myProgram *.o。 我们看看这份Makefile是怎么描述的。按照前面的写法,clean本应该是一个目标产物,但是我声明了.PHONY: clean,clean就变成了命令行的别名。GNU Make的文档是这样描述.PHONY的:

A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request.

GCC Compiler Options

  • 指定输出文件的名称:-o, e.g., gcc -o myProgram myProgram.c
  • 指定C语言标准:比如指定C99 --std=gnu99
  • 有一些与编译器警告相关的选项特别有用,建议开启:
    • -Wall 开启所有警告,换句话说,GCC会对它认为不可靠的代码发出warning。与这个选项同级别的选项还有-Wextra,不过这个选项包含一些跟风格相关的子选项,个人认为按需选取其中的子选项即可。
    • -Werror 把所有warning当作error来对待,原本的warning也会导致编译不通过。