Makefile的编写
——揭开makefile的神秘面纱
作者:zccst
从06年秋天开始接触Linux以来,竟然在今年3月份时对make及Makefile是什么,感觉还很玄,以为是高深的东西,可见在学校读书这几年里,我学到了多少知识。而且更重要的是养成了很多坏习惯,比如浮躁,重理论轻实践,而在学习理论时又不求甚解,看书时要么一下子看完了,很多不明白的问题一带而过,后来用到时还是不懂。要么看的很慢,看完一个内容,要很久才看得进去下一个内容。
言归正传,make工作原理:make是由开发大型程序与提高编写程序效率而提出。一个上百个源代码文件组成的项目,只对其中一个或少数几个做了修改,如果按照之前的方法直接使用gcc编译器,则不得不把整个项目里的所有文件都重新编译一遍,可想这将是多么大的工作量,而实际上那些没有改动的源代码文件根本不需要重新编译。make工具管理器能在执行makefile文件时,自动发现makefile文件里更新的文件,然后只编译他们,这样大大减少了工作量。
Makefile文件通常包括如下内容:
1, 需要由make工具创建的目标体(target),通常是目标文件或可执行文件。
2, 要创建的目标所依赖的文件。
3, 创建每个目标体时需要运行的命令。
Makefile的书写规则:
目标文件:依赖文件
(Tab)产生目标文件的命令
为方便讲清楚,举一个具体的例子:要求把3道题的题目存在二维数组中,调用自定义函数生成一个随机数,随机数传回主函数。主函数用随机数做下标,输出相应数组中的题目。
分析:此程序有主函数main和自定义函数fun_shuiji两个”.c”文件,把函数声明、用到的库函数写成一个独立的头文件”shuiji.h”,因此本题目程序分成3个文件。
步骤一:编写源码。
为简便起见,三道题目均简写为:“first.”,“second!”,“third?”。完整代码分别如下:
(1)vi 2-6-main.c 添加如下内容
#include "shuiji.h"
int main()
{
int n;
static char str[3][80] = {"firist.","second!","third?"};
n = fun_shuiji();
printf("the number you choice is:%d/n", n+1);
printf("the %d number:", n+1);
puts(str[n]);
}
(2)vi 2-6-shuiji.c 添加如下内容
#include "shuiji.h"
int fun_shuiji()
{
srand(time(NULL));
int a;
a = (rand() % 3);
return a;
}
(3)vi shuiji.h 添加如下内容
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int fun_shuiji();
步骤二:编写Makefile文件
Makefile有两种写法,一种是直接写,另一种则引用变量。根据需要选择其一即可。
1, 直接写。对照前面Makefile书写规则。
2-6:2-6-main.o 2-6-fun_shuiji.o
gcc 2-6-main.o 2-6-fun_shuiji.o -o 2-6
2-6-main.o:2-6-main.c shuiji.h
gcc 2-6-main.c -c
2-6-shuiji.0:2-6-shuiji.c
gcc 2-6-shuiji.c –c
注:Makefile文件里的gcc前面不是空格,而是按tab键生成。下同。
2, 引用变量。本文采用第2种方法,所以重点讲解。
CC = gcc
objects = 2-6-main.o 2-6-fun_shuiji.o
2-6:$(objects)
$(CC) $(objects) -o 2-6
2-6-main.o:2-6-main.c shuiji.h
gcc 2-6-main.c -c
2-6-shuiji.0:2-6-shuiji.c
gcc 2-6-shuiji.c -c
注:实际中的makefile文件里常常有4中变量,分别为预定义变量,用户自定义变量,自动变量和环境变量。例子中的“CC”是预定义变量,“object”是用户自定义变量。
常见预定义变量有:
Makefile中常见预定义变量
命 令 格 式 | 含 义 |
AR | 库文件维护程序的名称,默认值为ar |
AS | 汇编程序的名称,默认值为as |
CC | C编译器的名称,默认值为cc |
CPP | C预编译器的名称,默认值为$(CC) –E |
CXX | C++编译器的名称,默认值为g++ |
FC | FORTRAN编译器的名称,默认值为f77 |
RM | 文件删除程序的名称,默认值为rm –f |
ARFLAGS | 库文件维护程序的选项,无默认值 |
ASFLAGS | 汇编程序的选项,无默认值 |
CFLAGS | C编译器的选项,无默认值 |
CPPFLAGS | C预编译的选项,无默认值 |
CXXFLAGS | C++编译器的选项,无默认值 |
FFLAGS | FORTRAN编译器的选项,无默认值 |
常见自动变量有:
Makefile中常见自动变量
命令格式 | 含 义 |
$* | 不包含扩展名的目标文件名称 |
$+ | 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件 |
$< | 第一个依赖文件的名称 |
$? | 所有时间戳比目标文件晚的依赖文件,并以空格分开 |
命令格式 | 含 义 |
$@ | 目标文件的完整名称 |
$^ | 所有不重复的依赖文件,以空格分开 |
$% | 如果目标是归档成员,则该变量表示目标的归档成员名称 |
步骤三:用make编译程序
make –f makefile2.6
输出结果:
[root@localhost c]# make -f makefile2.6
gcc 2-6-main.c -c
gcc -c -o 2-6-fun_shuiji.o 2-6-fun_shuiji.c
gcc 2-6-main.o 2-6-fun_shuiji.o -o 2-6
注:make默认为在当前目录下找寻makefile文件,如果指定则后面加:“-f(ile) 文件名”。
步骤四:再次编译
为了体现Makefile的精华部分,再次编译,此时你会发现只编译了改动部分的源程序,而没改动的部分根本没有重新编译。
输出结果:
[root@localhost c]# make -f makefile2.6
gcc 2-6-main.c -c
gcc 2-6-main.o 2-6-fun_shuiji.o -o 2-6
步骤五:运行
./2-6
输出结果:
[root@localhost c]# ./2-6
the number you choice is:3
the 3 number:third?
在默认情况下,make(GNU)工作时执行的步骤:
1, make在当前目录下查找名字为“makefile文件”或“makefile文件夹”(注:一些make对大小写不敏感,但大多数支持小写makefile)。如果找到,make继续找文件中的第一个目标文件(target)。如例子中的2-6这个文件,如果不存在或2-6所依赖的后面的.o文件修改时间比2-6文件更新,则会执行后面所定义的命令来生成2-6文件。
2, 如果2-6所依赖的.o文件也存在,make会在当前文件中找.o文件的依赖性,如果找到,则会生成.o文件。
3, 当然,.c文件和.h文件如果找到,make会生成.o文件,然后用.o文件生成make的最终结果,也就是可执行文件2-6.
这就是整个make的依赖性,make会一层层地去找文件的依赖关系(有点像数据结构中的深度优先遍历,一直找到最深的节点,再一步步往回走),直到最终编译出第一个目标文件。如果在找寻过程中出现错误,则make退出并报错。如果在make找到了依赖关系,但冒号后面的文件不存在,make也不工作。
通过本文及一个例子,你已经初步了解make的强大功能,及makefile怎样帮助make完成它的使命,但要承认的是编写makefile不是一件轻松的事情,尤其对一个较大的项目而言,而且还存在版本的通用性问题。那么有没有一种更轻松的方式?答案是肯定的:“有,比如autotools系列工具”。使用autotools,你只需要输入简单的目标文件、依赖文件、文件目录等,就可以轻松地生成makefile了。autotools的具体使用请读者查阅相关资料。