多文件工程的编译-Makefile的简便写法

  通常我们在命令行使用GCC对程序进行编译,如果对于单个或者几个文件时比较方便的,但当工程中的文件逐渐增多甚至变得十分庞大的时候,使用GCC显然力不从心,不好管理。因此我们有必要编写一个Makefile来对工程进行管理。就以下工程目录进行学习。

生成可执行程序cacu,建立如下规则的Makefile文件。

#生成test,":"左边为目标,右边为依赖 。gcc后是命令
cacu:add_int.o add_float.o sub_int.o sub_float.o main.o
    gcc -o cacu add/add_int.o add/add_float.o \ (连接符)
            sub_int.o sub_float.o main.o
#生成add_int.o的规则
add_int.o:add/add_int.c add/add_int.h
    gcc -c -o add/add_int.o add/add_int.c
#生成add_float.o的规则
add_float.o:add/add_float.c add/add_float.h
    gcc -c -o add/add_float.o add/add_float.c
#生成sub_int.o的规则
sub_int.o:sub/sub_int.c sub/sub_int.h
    gcc -c -o sub/sub_int.o sub/sub_int.c
#生成sub_float.o的规则
sub_float.o:sub/sub_float.c sub/sub_float.h
    gcc -c -o sub/sub_float.o sub/sub_float.c
#生成main.o的规则
main.o:main.c add/add.h sub/sub.h
    gcc -c-o main.o main.c -Iadd -Isub
#清理的规则
clean:
    rm -f test add_int.o add_float.o sub_int.o             sub_float.o main.o

Makefile的规则:
Makefile的框架是由规则构成的,make命令执行时,先在Makefile文件中查找各种规则,对各种规则进行解析后,运行规则。规则的基本格式为
TARGET... :DEPENDEDS...
    COMAND
    ……
    ……
TARGET:规则所定义的目标。通常规则是最后生成的可执行文件的文件名或者为了生成可执行文件而依赖的目标文件的文件名,也可以是一个动作,称之为。伪目标。
DEPENDEDS:执行此规则所必须的依赖条件,例如生成可执行文件的目标文件。DEPENDEDS也可以是某个TARGET,这样就形成了TARGET之间的嵌套。
COMMAND:规则所执行的命令,即规则的动作,例如编译文件、生成库文件、进入目录等。动作可以是多个,每个命令占一行。规则的形式比较简单,要写好一个MakeEle需要注意一些地方,并对执行的过程有所了解。

1.规则的书写
在书写规则的时候,为了使Make租e更加清晰,要用反斜杠(\)将较长的行分解为多行, 例如将"rm-fcacu add/add_int. o add/add_tloat. o sub/sub_int. o sub/sub_float. o main. o"分解为了两行。命令行必须以Tab键开始,m工程序把出现在一条规则之后的所有连续的以Tab键开始的行都作为命令行处理。
注意:规则书写时要注意COMMAND的位置,COMMAND前面的空白是一个Tab键,不是空格。Tab告诉make这是一个命令行,make执行相应的动作。

2.目标
Makefile的目标可以是具体的文件,也可以是某个动作。例如目标cacu就是生成cacu的规则,有很多的依赖项,及相关的命令动作。而clean是清除当前生成文件的一个动作,不会生成任何目标项。

3.依赖项
依赖项是目标生成所必须满足的条件,例如生成cacu需要依赖main.o,main.o必须存在才能执行生成cacu的命令,即依赖项的动作在TARGET的命令之前执行。依赖项之间的顺序按照自左向右的顺序检查或者执行。例如,下面的规则

main. o main. c add/add. h sub/sub. h
  gcc-c-o main. o main. c-ladd-Isub
main.c、add/add.h和sub/sub.h必须都存在才能执行动作。gcc-c-omaiH.o main.c -ladd -lsub。。,当add/add.h不存在时,是不会执行规则的命令动作的,而且也不会检查sub/sub.h文件的存在,当然main.c由于在add/add.h依赖项之前,会先确认此项没有问题。

4.规则的嵌套
规则之间是可以嵌套的,这通常通过依赖项实现。例如生成cacu的规则依赖于很多的.o文件,而每个.o文件又分别是一个规则。要执行规则cacu必须先执行它的依赖项,即
add_int.o、add_float.o、sub_int.o、sub_float.o、main.o,这5个依赖项生成或者存在之后才进行cacu的命令动作。

5.文件的时间戳
make命令执行的时候会根据文件的时间戳判定是否执行相关的命令,并且执行依赖于此项的规则。例如对main.c文件进行修改后保存,文件的生成日期就发生了改变,再次调
用make命令编译的时候,就会只编译main.c,并且执行规则cacu,重新链接程序。

6、执行的规则
在调用make命令编译的时候,m工程序会査找MakeEle文件中的第1个规则,分析并执行相关的动作。例子中的第1个规则为cacu,所以m工程序执行cacu规则。由于其依赖项包含5个,第1个为add_int.o,分析其依赖项,当add/add_int.c add.h存在的时候,执行如下命令动作:

gcc-c-o addladd_int. o add/add_int. c

当命令执行完毕的时候,会按照顺序执行第2个依赖项,生成add/add_flaot.o.当第5个依赖项满足时,即main.o生成的时候,会执行cacu的命令,链接生成执行文件cacu.当把规则clean放到第一个的时候,再执行make命令不是生成cacu文件,而是清理文件。要生成cacu文件需要使用如下的make命令。

Debain #make cacu

7.模式匹配

在上面的Makefile中,main.o规则的书写方式如下

main. o :main. c add/add. h sub/sub. h
    gcc-c-o main. o main. c-Iadd-Isub

有一种简便的方法可以实现与上面相同的功能

main. o :%o %c
    gcc-c $<-o [email protected]

这种方法的规则main.o中依赖项中的。%o:%c。的作用是将TARGET域的.o的扩展
名替换为.c,即将main.o替换为main.c.而命令行的S〈表示依赖项的结果,即[email protected]
表示TARGET域的名称,即main.o。

在Makefile中使用用户自定义变量

定义OBJS变量表示目标文件:

  OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o

在调用OBJS的时候在前面加上$,并且变量的名称可以用括号括起来。例如,使用gcc的默认规则进行编译,cacu的规则可以采用如下形式

  cuca:gcc -o cacu $(OBJS)

用CC表示gcc,用CFLAGS表示编译选项,RM表示rm -f ,TARGET表示最终的生成目标cacu。

CC = gcc          (CC定义成为gcc)
CFLAGS = -Isub -Iadd    (加入头文件搜索路径sub,add文件夹)
TARGET = cacu       (最终生成的目标)
RM = rm -f         (删除的命令)

这样,之前冗长的Makefile可以简化为如下形式。

 1 CC = gcc
 2 CFLAGS = -Isub -Iadd -O2  (O2为优化)
 3 OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o
 4 TARGET = cacu
 5 RM = rm -f
 6 $(TARGET):$(OBJS)
 7   $(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
 8 $(OBJS):%.o:%.c  (将OBJS中所有扩展名为.o的文件替换成扩展名为.c的文件)
 9   $(CC) -c $(CFLAGS) $< -o [email protected]  (生成目标文件)
10 clean:
11   -$(RM) $(TARGET) $(OBJS)  - 表示忽略错误

由于CC的默认值已经为cc,RM的默认值为 rm -f,因此,如果在调用这些变量的时候未显式给出变量的定义,编译器就去调用其默认值。经过简化,可以得到以下形式:

1 CFLAGS = -Isub -Iadd -O2  (O2为优化)
2 OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o
3 TARGET = cacu
4 $(TARGET):$(OBJS)
5    $(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
6  $(OBJS):%.o:%.c  (将OBJS中所有扩展名为.o的文件替换成扩展名为.c的文件)
7    $(CC) -c $(CFLAGS) $< -o [email protected]  (生成目标文件)
8  clean:
9    -$(RM) $(TARGET) $(OBJS)  - 表示忽略错误

Makefile很智能(会自动推导,使用默认的方式生成目标文件),可以再简化,就可以得到如下形式:

1 CFLAGS = -Isub -Iadd -O2  (O2为优化)
2 OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o
3 TARGET = cacu
4 $(TARGET):$(OBJS)
5    $(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
6  clean:
7   -$(RM) $(TARGET) $(OBJS)  - 表示忽略错误

Makefile之博大精深,暂时学习到这里(待续)。。。。

时间: 2024-10-03 08:58:18

多文件工程的编译-Makefile的简便写法的相关文章

工程管理之makefile与自动创建makefile文件过程

(风雪之隅 http://www.laruence.com/2009/11/18/1154.html) Linux Makefile自动编译和链接使用的环境 想知道到Linux Makefile系统的真相么,想知道Linux Makefile系统中藏有的内在奥义么,只有我来给大家全面讲解介绍Linux Makefile系统作为Linux下的程序开发人员,大家一定都遇到过Linux Makefile,用make命令来编译自己写的程序确实是很方便.一般情况下,大家都是手工写一个简单Linux Mak

【转】多文件目录下makefile文件递归执行编译所有c文件

首先说说本次嵌套执行makefile文件的目的:只需make根目录下的makefile文件,即可编译所有c文件,包括子目录下的. 意义:自动化编译行为,以后编译自己的c文件时可把这些makefile文件直接复制到相应目录即可方便编译出所有文件.这些makefile文件是通用的,只需根据自己的工程情况改动少许内容即可.下面会说. 总体思路是:把目标文件放在debug文件夹下的obj目录下,把最终的二进制文件放在debug文件夹下的bin目录下;如何递归编译所有除了debug目录下的makefile

《Effective C++》:条款31:将文件间的编译依存关系降至最低

假如你在修改程序,只是修改了某个class的接口的实现,而且修改的是private部分.之后,你编译时,发现好多文件都被重新编译了.这种问题的发生,在于没有把"将接口从实现中分离".Class的定义不只是详细叙述class接口,还包括许多实现细目: class Person{ public: Person(const std::string& name, const Date& birthday, const Address& addr); std::strin

我的 FPGA 学习历程(08)&mdash;&mdash; 多文件工程的建立

这篇的重点是讲解如何使用多文件工程.伴随着学习的深入,编写的代码量会变得越来越大,尤其是当工程中有相互调用的模块关系的时候,如果还使用一个文件描述整个工程,就会使得文件变得非常的长而且难以查阅. 这篇新建一个名为 add4 的工程,目标是设计一个由四个一位全加器构成的四位加法器.在 verilog 语法中可以直接使用 + 运算符来构建加法器,当然在实际工程中生成一个加法器既不需要这么多工作也不需要这么麻烦,但这篇存在的价值是作为多文件工程的练习. 一位全加器的代码如下: 在 Quaruts 工程

C编译: makefile基础

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 在编译一个大型项目的时候,往往有很多目标文件.库文件.头文件以及最终的可执行文件.不同的文件之间存在依赖关系(dependency).比如当我们使用下面命令编译时: $gcc -c -o test.o test.c $gcc -o helloworld test.o 可执行文件helloworld依赖于test.o进行编译的,而test.o依赖于test.c. 依赖关系 在我们编

建立开发板文件,测试编译环境

U-Boot没有支持S3C2440,移植仍是U-Boot支持的SBC2410的文件作为蓝本进行移植.所以移植要做的就是针对S3C2440和S3C2410的不同,以及SBC2410和mini2440开发板的外设不同作相应的修改,并增加新的功能. 6.1 建立开发板文件,测试编译环境 6.1.1 修改顶层Makefile 目的:定义交叉编译工具链和开发板配置选项. CROSS_COMPILE = arm-linux- #set default to nothing for native builds

Android工程的编译过程

现在很多人想对Android工程的编译和打包进行自动化,比如建立每日构建系统.自动生成发布文件等等.这些都需要我们对Android工程的编译和打包有一个深入的理解,至少要知道它的每一步都做了什么,需要什么环境和工具,输入和输出是什么.那么我们就来挖掘一下Android的编译过程中的细节. 首先,我们假定你的系统(什么系统都行,不限于Linux还是Windows系统,当然,我在这里默认使用Linux系统来举例子,但在 Windows中几乎没有什么差别)已经安装了JDK和Android SDK.再假

应用 JD-Eclipse 插件实现 RFT 中 .class 文件的反向编译

概述 反编译是一个将目标代码转换成源代码的过程.而目标代码是一种用语言表示的代码 , 这种语言能通过实机或虚拟机直接执行.文本所要介绍的 JD-Eclipse 是一款反编译的开源软件,它是应用于 Eclipse 开发平台的插件,它可以帮助开发人员在调试程序的过程中显示所有的 Java 源代码,方便了开发人员的开发工作. 回页首 JD-Eclipse 的安装及配置 高级语言源程序经过编译变成可执行文件,反向编译就是其逆过程.但是由于反向编译的复杂性,通常不能把可执行文件变成高级语言源代码,只能转换

Linux 内核模块编译 Makefile

驱动编译分为静态编译和动态编译:静态编译即为将驱动直接编译进内核,动态编译即为将驱动编译成模块. 而动态编译又分为两种: a -- 内部编译 在内核源码目录内编译 b -- 外部编译 在内核源码的目录外编译 二.具体编译过程分析   注:本次编译是外部编译,使用的内核源码是Ubuntu 的源代码,而非开发板所用linux 3.14内核源码,运行平台为X86. 对于一个普通的linux设备驱动模块,以下是一个经典的makefile代码,使用下面这个makefile可以完成大部分驱动的编译,使用时只