`
tomhibolu
  • 浏览: 1411637 次
文章分类
社区版块
存档分类
最新评论

Linux Make(Makefile)由浅入深的学习与示例剖析

 
阅读更多

经过长时间学习和研究linux GNU make工程管理器 ,现在把学习心得与大家分享一下,希望本文能教会您一些有用的东西。

make工具,是所有想在Linux/Unix系统上编程的用户都需要且必须掌握的工具。如果您写的程序没有用到make工具,则说明您写的程序仅仅是个人练习小程序,称不上有实用价值的程序,因此我们必须学习、掌握并灵活运用它。

在Linux/UNIX 系统中,习惯使用 Makefile或makfile 文件作为make命令目标文件。 Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互依赖关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。

一、多文件编译的总体结构

如下图所示, 本示例 共包含 float类型加法、加法头函数、int类型加法、main主函数、float类型减法、减法头函数、int类型减法

主函数

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include"add.h"
  2. #include"sub.h"
  3. #include<stdio.h>
  4. int main()
  5. {
  6. int x,y;
  7. float a,b;
  8. x=5;
  9. y=2;
  10. a=5.5;
  11. b=2.2;
  12. printf("%d+%d=%d/n" ,x,y,add_int(x,y));
  13. printf("%3.1f+%3.1f=%3.1f/n" ,a,b,add_float(a,b));
  14. printf("%d-%d=%d/n" ,x,y,sub_int(x,y));
  15. printf("%3.1f-%3.1f=%3.1f/n" ,a,b,sub_float(a,b));
  16. return 0;
  17. }

加法头函数

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /*add.h*/
  2. #ifndef_ADD_H_
  3. #define_ADD_H_
  4. extern int add_int(int x,int y);
  5. extern float add_float(float x,float y);
  6. #endif

int类型加法

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /*add_int.c*/
  2. int add_int(int x,int y)
  3. {
  4. return x+y;
  5. }

float类型加法

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /*add_float.c*/
  2. float add_float(float x,float y)
  3. {
  4. return x+y;
  5. }
  6. ~

减法头函数

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /*sub.h*/
  2. #ifndef_SUB_H_
  3. #define_SUB_H_
  4. extern int sub_int(int x,int y);
  5. extern float sub_float(float x,float y);
  6. #endif

int类型减法

  1. /*sub_int.c*/
  2. int sub_int(int x,int y)
  3. {
  4. return x-y;
  5. }

float类型减法

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /*sub_float.c*/
  2. float sub_float(float x,float y)
  3. {
  4. return x-y;
  5. }

二、方法1 ( 多文件编译

直接在Linux的Shell环境中,分别依次利用gcc -c *.c -o *.o命令进行编译,并链接生成可执行文件,具体做法如下:

依次编译上述文件:

编译后的结果文件:

然后,直接利用gcc -o main add_int.o add_float.o sub_int.o sub_float.o main.o命令,链接、编译成目标可执行文件main

最后,输入 ./main 运行结果

评析: 此方法遵照单文件编译方法,过程清晰、直观易懂;但效率很低,在编译文件数量很大或源文件修改时,此方法效率很低,且难以维护

三、方法 2 ( 多文件编译——使用makefile

此方法为了避免方法1的不足,利用Linux GNU make工程管理器,进行编译、管理源文件。

首先,了解一下make和makefile。 GNU make是一个工程管理器,专门负责管理、维护较多文件的处理,实现自动化编译。如果一个工程项目中,有成百上千个代码源文件,若其中一个或多个文件进过 修改,make就需要能够自动识别更新了的代码,不需要像方法1一样逐个输入编译冗长的命令行,就可以完成最后的编译工作。make执行时,自动寻找 makefile(Makefile)文件,然后执行编译工作。因此,我们需要自己编写makefile文件(Makefile与makefile都可以 直接被make命令识别,下同。但Linux区分大小写)来管理、维护工程文件,提高实际项目的工作效率。

其次,需要注意Linux makefile(Makefile)文件的编写规范和方法:

1、需要由make工具创建目标体target,即通常的目标文件或可执行文件

2、声明并给出创建的目标体所依赖的文件(dependency-file)

3、编写完成创建每个目标体时所需要执行的命令(command)

具体格式如下:

target: dependency-file1 dependency-file2 dependency-file3 ...

command

target:规划的目标。通常是程序中间体或最后所需要生成的文件名,如 *.o或obj可执行文件的名称。此外,target目标也可以是make执行动作的名称,如clean等

dependency-file:规则的依赖。生成规则目标所需要的文件名列表,通常是一个目标依赖于一个或多个文件。

command:规则的命令。make程序所执行的的动作,可以为shell命令或者在shell下执行的程序。一个规则可以有多条命令,每条命令 占一行。 在此特别需要注意的是每条命令行开始必须以Tab字符缩进开始,Tab缩进字符会告诉make命令此行是一个命令行,make按照命令完成此行相应的动 作。这是在书写makefile(Makefile)文件时最易忽视和犯错的地方,而且大多比较隐蔽。

命令实质上市对任何一个目标的依赖文件发生变化后重建目标的动作描述。一个目标可以没有依赖而只有动作,即只有命令,如clean。此目标只有命令,没有依赖,主要作用是用来删除make过程中产生的中间文件(*.o),做收尾清理工作。

最后,上面均是纸上谈兵,现在我们来看具体实例,以直观、具体、详尽的解释makefile文件的编写方法和规则。

方法1可以用如下makefile文件代替,makefile编写如下:

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #target:dependency-file
  2. main:main.oadd_int.oadd_float.osub_int.osub_float.o
  3. #NOTE:Tabbeforecommand
  4. gcc-omainmain.oadd_int.oadd_float.osub_int.osub_float.o
  5. main.o:main.cadd.hsub.h
  6. gcc-cmain.c-omain.o
  7. add_int.o:add_int.cadd.h
  8. gcc-cadd_int.c-oadd_int.o
  9. add_float.o:add_float.cadd.h
  10. gcc-cadd_float.c-oadd_float.o
  11. sub_int.o:sub_int.csub.h
  12. gcc-csub_int.c-osub_int.o
  13. sub_float.o:sub_float.csub.h
  14. gcc-csub_float.c-osub_float.o
  15. clean:
  16. rm-f*.omain

说明:

#表示注释,其后的在编译预处理时,将被全部删除不执行

gcc -c 编译C语言源文件,编译生成目标文件 *.o

gcc -o 定义生成文件名称,可以为 *.o(目标文件)和 main(可执行文件)

rm -f *.o main 强制删去该目录下的所有*.o 目标文件和main可执行文件

在shell命令行执行make命令

查看make执行makefile文件后的编译结果如下:

与方法1的结果基本一致,并且直接生成了可执行文件main

最后,输入 ./main 运行结果

此方法,与方法1运行结果,完全一致!

评析: 方法2利用makefile文件,进行项目所有文件的编译管理,可保存、易修改,且编译执行效率高,大大减轻了每次编译的工作量

方法2,仅仅是最为初级的makefile项目管理格式,现在我们将逐步对其进行优化、改进

四、方法 3 ( 使用变量 —— 改进1)

在编写makefile文件时,各部分引用变量的格式规范

1、 make变量引用不同于Linux Shell变量引用规则,而是需加括号,即 $(Var) 格式,无论 Var 是单字符变量名还是多字符变量名均可。

2、在命令行中出现的Shell变量,引用Shell的 $tmp 格式,一般为执行命令过程中的临时变量,不属于makefile变量,而是Shell变量。

3、对出现在命令行中的make变量,同样使用 $(Command) 格式来引用。

纸上得来终觉浅,绝知此事要躬行。输入vim makefile命令,在Shell 利用vim编辑器来编写makefile文件,具体写法如下:

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. OBJ=main.oadd_int.oadd_float.osub_int.osub_float.o
  2. make:$(OBJ)
  3. gcc-cmain.c-omain.o
  4. add_int.o:add_int.cadd.h
  5. gcc-cadd_int.c-oadd_int.o
  6. add_float.o:add_float.cadd.h
  7. gcc-cadd_float.c-oadd_float.o
  8. sub_int.o:sub_int.csub.h
  9. gcc-csub_int.c-osub_int.o
  10. sub_float.o:sub_float.csub.h
  11. gcc-csub_float.c-osub_float.o
  12. clean:
  13. rm-f$(OBJ)main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1和方法2运行结果,完全一致!

评析: 方法3利用makefile变量,引入变量使makefile更加简洁、清晰,便于分组、统一维护,编译管理更加高效

五、方法 4 ( 使用自动推导 —— 改进2)

编写makefile文件,让make命令自动推导。只要make看到了 *.o 文件,它就会自动把与之对应的 *.c 文件加到依赖文件中,并且gcc -c *.c 也会被推导出来,所以makefile就简化啦。 此外,我们使用 $(Command) 格式,来引用命令变量。具体做法如下

首先,在Shell输入 vim makefile ,利用VIM编辑makefile文件内容

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. CC=gcc
  2. OBJ=main.oadd_int.oadd_float.osub_int.osub_float.o
  3. make:$(OBJ)
  4. $(CC)-omain$(OBJ)
  5. main.o:add.hsub.h
  6. add_int.o:add.h
  7. add_float.o:add.h
  8. sub_int.o:sub.h
  9. sub_float.o:sub.h
  10. PHONY:clean
  11. clean:
  12. rm-f$(OBJ)main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1、方法2和方法3的运行结果,完全一致!

评析: 方法4在makefile文件中,引入参数变量和命令变量,利用make命令自动推导依赖文件,来编译系统,高效但不太直观,高手可用

六、方法5( 使用自动变量($^ $< $@) —— 改进3)

在编写makefile文件中,有三个非常有用的变量,即分别是 $@ $^ $< 其代表的具体意义如下:

$@ : 目标文件

$^ : 所有依赖文件

$< : 第一个依赖文件

具体使用方法如下例所示

首先,在Shell输入 vim makefile ,利用VIM编辑makefile文件内容

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. CC=gcc
  2. OBJ=main.oadd_int.oadd_float.osub_int.osub_float.o
  3. make:$(OBJ)
  4. $(CC)-o$@$^
  5. main.o:main.cadd.hsub.h
  6. $(CC)-c$<
  7. add_int.o:add_int.cadd.h
  8. $(CC)-c$<
  9. add_float.o:add_float.cadd.h
  10. $(CC)-c$<
  11. sub_int.o:sub_int.csub.h
  12. $(CC)-c$<
  13. sub_float.o:sub_float.csub.h
  14. $(CC)-c$<
  15. PHONY:clean
  16. clean:
  17. rm-f$(OBJ)main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1、方法2、方法3和方法4的运行结果,完全一致!

评析: 方法5在makefile文件中,引入参数变量、命令变量和自动变量,此方法编译系统,高效但不太直观,特别是维护修改不便,高手可秀。

七、方法6 ( 使用缺省规则(..c.o:) —— 改进4)

在依次使用了上述变量、自动推导、自动变量规则后,或许还有人认为太复杂,想寻求更简洁的方法,这里我们再介绍makefile缺省规则。

makefile的缺省规则如下:

..c.o:

gcc -c $<

这个规则表示,所有的 *.o 目标文件都是依赖于相应的 *.c 源文件的, 例如 main.o 依赖于 main.c 。 具体makefile编写方法如下

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. CC=gcc
  2. main:main.oadd_int.oadd_float.osub_int.osub_float.o
  3. $(CC)-o$@$^
  4. ..c.o:
  5. $(CC)-c$<
  6. clean:
  7. rm-f*.omain

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1、方法2、方法3、方法4和方法5的运行结果,完全一致!

评析: 方法6在makefile文件中,引入缺省规则,是make自动推导,非常简洁、高效,但不太直观,特别是具体文件依赖关系不清,维护较不便。

==================================================================================

综合评析: 以上方法,由浅入深,点评剖析,重在灵活运用。方法2直观易懂,方法3引入变量简洁,这两种方法便于管理维护,推荐使用。方法4、方法5和方法6,主要是深入剖析makefile博大精深的编写使用方法,在具体项目管理实践中,可以选择借鉴使用,适合内功深厚者。

以上示例程序,均已测试并运行通过 ,具体测试编译环境如下:

Linux系统: Red Hat Linux Server 5.2

VIM编辑器:VIM - Vi IMproved 7.0

系统的环境:Linux安装在VMWare 7.0 虚拟机上

==================================================================================

编译Bug与Debug小结

1、makefile: 4: *** 遗漏分隔符 。 停止 。 错误提示,如下图

分析与处理: 以上错误提示,说明makefile文件第4行,分隔符格式不正确,导致错误。错误详见下图

从上图可见,第4行为command命令行,应该如上述方法2中强调所说,命令行应当Tab分隔缩进 ,解决后如下图所示:

2、make: main 是最新的。 错误提示,如下图

这是因为该文件目录中,已经存在了目标可执行文件 main ,请见下图

解决办法: 输入 rm main 或者 rm -f main 命令,先删去 main 文件,然后再输入 make 命令 ,进行编译链接即可

编译链接成功后,直接利用 ./main 运行生成的目标可执行文件即可啦 ^_^

分享到:
评论

相关推荐

    Linux_Make(Makefile)由浅入深的学习与示例剖析.doc

    ### Linux Make(Makefile)由浅入深的学习与示例剖析 #### 一、Make工具简介及重要性 Make工具是Linux/Unix环境下极其重要的工程管理工具之一,它主要用于自动化构建项目,尤其对于大型项目来说,能够显著提高开发...

    linux下驱动程序模块编程多文件makefile编写示例

    本示例主要探讨的是如何为Linux驱动程序模块编写一个多文件的Makefile,以便于编译和加载。 首先,我们需要理解Makefile的基本结构和规则。Makefile由一系列的目标(target)和依赖(dependency)组成,目标通常是...

    Linux内核Makefile分析

    通过深入分析Linux2.6内核的Makefile,我们可以揭示其编译过程的奥秘,这对于理解和改进操作系统、优化编译流程以及学习Makefile语法都至关重要。 Makefile是GNU Make工具使用的配置文件,用于自动化构建项目。在...

    linux的makefile文件例子

    本文将详细解析`Makefile`的基本结构、规则和常见指令,通过提供的"实验8_2"和"实验8_1"文件名,我们可以推测这可能是一些与`Makefile`相关的实践项目。 ### 1. `Makefile`概述 `Makefile`是一个文本文件,包含了...

    linux_makefile_详解.pdf

    Makefile 的学习和掌握是每一个专业程序员的必备技能,尤其是在 Linux/Unix 平台上。它不仅是自动化构建的基础,也是提升软件工程质量和效率的重要工具。通过深入理解 Makefile 的工作原理和语法结构,开发者能够更...

    Linux_Makefile实验.pdf

    Linux Makefile 实验文档深入探讨了Makefile在Linux和Unix系统中的应用,以及如何使用GNU make工具来自动化编译和构建项目。Makefile是一个自动化编译的工具,能够根据文件的时间戳来决定哪些文件需要编译,从而提高...

    make以及makefile学习

    以下详细阐述了make以及makefile的学习要点: 1. Makefile概述 Makefile是一个脚本文件,其中包含了编译和链接项目所需的各种规则。它描述了项目中哪些文件需要先编译,哪些文件需要后编译,哪些文件依赖于其他文件...

    LINUX2.6内核makefile详解

    "LINUX2.6内核makefile详解" Linux 2.6 内核 Makefile 详解是 Linux 内核开发中非常重要的一部分。Makefile 是一个脚本文件,用于描述如何编译和构建 Linux 内核。该文件是 Linux 内核开发的核心组件之一,对开发...

    Linux 内核makefile浅析

    在Linux内核开发中,`Makefile`扮演着至关重要的角色,它是构建和编译内核的核心。本文将深入浅出地探讨Linux内核的`Makefile`及其配置系统,旨在帮助初学者理解这一复杂的构建过程。 首先,Linux内核的配置系统由...

    linux makefile 工程管理器

    ### Linux Makefile工程管理器知识点详解 #### 一、Makefile概述 ...以上是关于 **Linux Makefile 工程管理器** 的详细介绍,通过学习这些知识点,可以更加深入地理解Makefile的工作原理及其在实际项目中的应用。

    Linux通过Makefile模型

    通过该Makefile模型,可以自己修改源文件和头文件的路径后,执行make和make clean即可,通用、快速完成makefile的编写

    linux下make和makefile用法

    "Linux 下 Make 和 Makefile 用法" Make 工具是 Linux 系统中一个非常重要的编译工具,它可以根据 Makefile 文件中的描述来自动维护编译工作。Makefile 文件是 Make 工具的核心组件,它定义了源程序之间的相互关系...

    Linux下makefile教程

    Linux下makefile教程 makefile 是一个非常重要的文件,在 Unix 下的软件编译中,makefile 起着至关重要的作用,它定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于...

    linux Makefile 教程

    ### Linux Makefile 教程详解 #### 一、引言 在现代软件开发过程中,Makefile 起着至关重要的作用,特别是在 Linux 和其他 Unix-like 操作系统中。本文将深入探讨 Makefile 的基本概念、编写技巧以及如何利用 ...

    make使用和makefile示例

    总的来说,理解和掌握`make`与`Makefile`对于Linux下的软件开发至关重要。它们提供了自动化构建的框架,使得开发者可以专注于代码编写,而非手动管理编译过程。通过不断实践和优化`Makefile`,我们可以创建出高效、...

    Linux GNU Makefile中文版手册 .rar

    **GNU/Linux Makefile详解** Makefile是GNU Make工具的核心,它是构建软件项目的关键文件,用于自动化编译、链接和其他构建过程。在Linux环境下,Makefile的重要性不言而喻,它帮助开发者有效地管理复杂的源代码...

    Make工具及makefile规则学习

    #### 二、Make与Makefile的关系 Make工具和Makefile之间存在紧密的联系: - **调用与被调用关系**:Make是一个命令行工具,用于读取Makefile中的规则并执行相应的动作。 - **Make是命令名**:用户在命令行中输入`...

    Linux下Makefile文件的编写

    ### Linux下Makefile文件的编写 #### Makefile文件的基本概念与作用 Makefile是一种用于自动化构建过程的脚本文件,在Linux环境下广泛应用于软件项目的编译、链接等操作中。通过定义一系列规则,Makefile能够自动...

Global site tag (gtag.js) - Google Analytics