makefile简介及编写之一

来源:互联网 发布:iptables内网端口转发 编辑:程序博客网 时间:2024/06/05 07:57

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

关于程序的编译和链接

在此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

Makefile 介绍

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
    1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
    2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
    3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

Makefile的规则

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

注意:如果“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_Hint fun1(void);int fun2(void);int fun3(void); #endif
一般的,我们会使用
gcc -o hello hello.c function.c -I.

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

  1. 当你把编译指令丢失或者换电脑的时候,这样效率会很低;
  2. 当我们只修改了一个.c文件时,每一次都将必须重新编译所有的文件,非常耗时,不划算。
所以我们需要用 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 = helloCC = gccCFLAGS = -I.$(TARGET): hello.c function.c   $(CC) -o $(TARGET) hello.c function.c $(CFLAGS)

这里我们新定义了三个变量 TARGET、CC 和 CFLAGS 。TARGET是目标文件名,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 = helloCC = gccCFLAGS = -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),即冒号(:)的右边;
  • $<:第一个依赖文件。
在这个版本中,我们把所有 object 文件作为 OBJ 的一部分:
TARGET = helloCC = gccCFLAGS = -I.DEPS = function.hOBJ = 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“就可以实现清理工作。



1 0
原创粉丝点击