Makefile文件编写

教程 8796浏览 5评论

Makefile 是 Linux 下程序开发的自动化编译工具,用以识别编译目标源文件及其依赖关系,并且有着高效的编译效率。每次执行 make 时,就能够自动寻找 Makefile(makefile)文件,执行编译工作。Makefile拥有很多复杂的功能,为了简化问题的复杂性,本文仅和大家讨论针对单目录下的C/C++项目开发,如何写一个通用的 Makefile。

我们一般在 Linux 中经常看到项目用有 Makefile,而在 Windows 中很少看到,原因是因为 Windows 上的IDE工具已经自动生成了Makefile 文件,编译工程的时候自动调用 Makefile 进行编译,所以在 Windows 上我们经常是在不知情的情况下和Makefile打交道。

一个Makefile中通常包含下面内容:1、需要由make工具创建的目标体(target),通常是目标文件或可执行文件。2、要创建的目标体所依赖的文件(dependency)。3、创建每个目标体时需要运行的命令(command)。 基本语法规则如下:

makefile文件

注意:如果“command”不和目标文件所在一行时,在command前要加 Tab 键。

开始

下面以一个简单的程序为例,包括主程序(hello.c)、函数代码(function.c)、头文件(function.h),内容如下。

主函数hello.c:

#include <stdio.h> 
#include "function.h"
int main()
{
    fun1();
    fun2();
    fun3();
    return 0; 
}

函数function.c:

#include<stdio.h>
int fun1() 
{ 
    printf("This is first function!\n"); 
    return 0; 
} 
 
int fun2() 
{ 
    printf("This is second function!\n"); 
    return 0; 
} 
 
int fun3() 
{ 
    printf("This is third function!\n"); 
    return 0; 
}

头文件function.h:

#ifndef _FUN_H
#define _FUN_H

int fun1(void);
int fun2(void);
int fun3(void);
 
#endif

一般的,我们会使用

gcc -o hello hello.c function.c -I.

来编译。“-I.”是指 gcc 在当前目录(.)下寻找头文件。如果不用 makefile,在“测试-修改-调试”过程中,我们必须不停地在终端中按上下键来寻找最后的那条编译指令。这种编译方法有两个缺陷:

  1. 当你把编译指令丢失或者换电脑的时候,这样效率会很低;
  2. 当我们只修改了一个.c文件时,每一次都将必须重新编译所有的文件,非常耗时,不划算。

简单makefile

所以我们需要用 Makefile 文件来统一管理这种编译规则,这样只要我们在目录下敲入“make”命令,就可以编译了。本例最简单的 Makefile 写法:

hello: hello.c function.c
    gcc -o hello hello.c function.c -I.

注意:第一行中并没有任何参数,只是在冒号(:)后列出编译中所需的文件,当第一行中的任何文件中更改时,make 就知道 hello  需要重新编译了。还有一点非常重要:gcc 前面必须有一个Tab,在任何指令之前都要有一个Tab,不然make就会罢工的。

现在我们已经解决了问题1,不用上下按箭头了,但是对于问题2依旧没有很好地解决。

变量

为了让事情变得更通用,我们将 Makefile 改成:

TARGET = hello
CC = gcc
CFLAGS = -I.

$(TARGET): hello.c function.c
    gcc -o $(TARGET) hello.c function.c $(CFLAGS)

这里我们新定义了三个变量 TARGET、CC 和 CFLAGSTARGET是目标文件名,CC是编译器,CFLAGS是编译用的参数。make 会先分别编译 .c 文件,然后生成可执行文件 hello。

这种形式的makefile在小项目中非常有效,但是有一个遗憾:include 头文件的变动。如果我们修改了 function.h 文件,make是不会重新编译 .c 文件的,事实上我们需要重新编译。为了解决这一问题,我们必须告诉 make 所有的 .c 文件依赖于 .h 文件。

一般在书写Makefile时,各部分变量引用的格式如下:

  1. make变量(Makefile 中定义的或者是 make 的环境变量)的引用使用“$(VAR)”格式,无论“VAR”是单字符变量名还是多字符变量名。
  2. 出现在规则命令行中shell变量(一般为执行命令过程中的临时变量,它不属于Makefile变量,而是一个shell变量)引用使用shell的“$tmp”格式。
  3. 对出现在命令行中的 make 变量同样使用“$(CMDVAR)” 格式来引用。

自动变量

自动变量可以让 make 看到一个.o文件,自动把对应的 .c 文件加到依赖文件中,这样 Makefile 就简化了。我们可以在 Makefile 中增加一条规则:

TARGET = hello
CC = gcc
CFLAGS = -I.
DEPS = function.h

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

$(TARGET): hello.c function.c
    $(CC) -o $@ $^ $(CFLAGS)

首先定义 DEPS,声明 .c 文件所依赖的 .h 文件。然后我们定义一条规则,为所有的 .c 文件生成一个 .o 文件。最后再定义生成目标文件。其中,

  • -c:意味着产生object文件;
  • $@:目标文件(%.o),即冒号(:)的左边;
  • $^:所有的依赖文件(%.c),即冒号(:)的右边;
  • $<:第一个依赖文件。

OBJ和伪命令

在这个版本中,我们把所有 object 文件作为 OBJ 的一部分:

TARGET = hello
CC = gcc
CFLAGS = -I.
DEPS = function.h
OBJ = hello.o function.o

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

$(TARGET): $(OBJ)
    $(CC) -o $@ $^ $(CFLAGS)

这一步中会生成两个 .o 文件和目标文件,有时候我们需要清除它们,就要加入一个伪目标:clean,在最后加上这样的两行:

clean:
    rm -f $(TARGET) *.o

有的目标可能没有依赖,只有动作(指定的命令)。比如上面的“clean”伪目标,没有依赖,只有命令。它所指定的命令用来删除 make 过程产生的中间文件,我们使用”make clean“就可以实现清理工作。

目录结构

如果我们想把  .h 文件放在 include 目录下,.c 文件放在 src 目录下,以及一些本地的库放在 lib 目录下,同时我们想把 .o 文件整理一下,避免整个目录的凌乱。在这个版本中,我们同时还包含任何要包含的库(比如说math库-lm)。这份 Makefile 将放在 src 目录下。目录结构如下:

Makefile 结构

Makefile 文件内容相应改为:

TARGET = hello
IDIR = ../include
CC = gcc
CFLAGS = -I$(IDIR)
ODIR = obj
LDIR = ../lib
LIBS = -lm
DEPS = function.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hello.o function.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

$(TARGET): $(OBJ)
    gcc -o $@ $^ $(CFLAGS) $(LIBS)

clean:
    rm -f $(TARGET) $(ODIR)/*.o *~ $(IDIR)/*~

其中 patsubst 是 Makefile 的一个函数,实现替换通配符,它包含 3 个参数:需要匹配的式样,用什么来替换它,需要被处理的由空格分隔的字符串。如:patsubst %,$(ODIR)/%,$(_OBJ) 实现把$ (_OBJ) 中的所有变量前面都加上$(ODIR)/

恭喜!目前我们已经有了一个不错的 Makefile,根据这个,我们能维护中小型的工程。当然我们能增加一些更复杂的规则,甚至创造一些规则。

makefile文件工作

参考资料

  • 一个简单的通用Makefile实现:http://www.cnblogs.com/fanzhidongyzby/p/3141041.html
  • makefile的简单使用:http://blog.csdn.net/bdss58/article/details/40667907
  • 手动建立makefile简单实例解析:http://wenku.baidu.com/view/0911e4abd1f34693dbef3e05.html
  • 跟我一起写 Makefile:http://www.chinaunix.net/old_jh/23/408225.html
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (5)

  1. 跟我一起写 Makefile 是陈皓的一系列很有名的古文,原始出处是http://blog.csdn.net/haoel/article/details/2886
    Yu 4年前 (2014-12-26) 回复 编辑
    • 逗B吧,这是翻译国外的文章,弄个谷歌翻译,我自己都能整出来
      温柔绕指尖 4年前 (2014-12-27) 回复 编辑
      • 我特指文末"跟我一起写 Makefile:http://www.chinaunix.net/old_jh/23/408225.html"这个reference
        Yu 4年前 (2014-12-31) 回复 编辑
  2. 不错。。。。。。。。。。。。。。
    zjf_linux 4年前 (2015-01-31) 回复 编辑
  3. 好文,有才!
    江夏离 3年前 (2015-12-12) 回复 编辑