当前位置: 首页 > news >正文

Linux下的Make与Makefile

转载自:http://www.cpplive.com/html/1776.html

对于LinuxUnix系统而言,make是一个极其重要的编译命令,我们在开发项目或者安装应用软件时,经常要用到makemake install,对于一个包含几十、几百甚至成千上万个源文件的项目,如果每次都要键入gccg++等命令来进行编译的话,那对于程序员简直就是一场噩梦,而使用makemakefile工具便可以简洁明了地理顺各个源文件之间纷繁复杂的相互关系,将大型项目分解成多个更易于管理的模块,自动完成编译工作,并且可以只对程序员上次编译后修改过的部分进行编译。

因此,有效的利用makemakefile工具可以大大提高程序开发的效率。同时也极大地减轻了Linux下应用程序安装的难度。接下来,就让我们来详细了解一下make及其描述文件makefile

一、make

1、程序的诞生

无论是C语言还是C++,我们通常首先将源文件编译成中间代码,在Windows下是“.obj”文件,在LinuxUnix下是“.o”文件,即目标文件(Object File),这个过程叫作编译(Compile);然后再把生成的目标文件合成可执行文件,这个过程叫作链接(Link)。编译时,编译器负责检查语法、函数与变量的声明正确与否,只要检测过关,就将生成目标文件,一般而言,每个源文件都对应一个目标文件。

链接时,编译器负责链接函数和全局变量,编译器并不关心函数所在的源文件,只关心目标文件,有时候因为源文件太多,编译时生成的目标文件很多,导致在链接时需要指定一大堆的目标文件,显然很不方便,这时候我们可以将目标文件打包成一个库文件,Windows下这种库文件也就是“.lib”文件,Linux下是“.a”或“.so”文件。

总之,编译时,我们通常先将源文件编译成目标文件,再对目标文件进行链接操作,程序便诞生了。

2make的工作原理

make工具最基本的功能是调用Makefile文件,通过Makefile文件来描述程编译的整个过程,不必每次敲gccg++等命令来完成编译工作。当然,Makefile文件需要按照一定的语法进行编写,说明如何编译各个源文件并链接生成可执行文件,以及各个文件之间的依赖关系。

如下图所示,演示了一个简单的make命令编译过程:

在该目录下,有三个文件,add.c main.c Makefile。内容如下:

//add.c

int add(int a, int b)

{

return(a+b);

}


//main.c

#include"stdio.h"
#include"stdlib.h"

int add(int a,int b);

int main(int argc, char**argv)

{

int a=atoi(argv[1]);

int b=atoi(argv[2]);

printf("%d+%d=%d\n",a,b,add(a,b));

return 0;

}


//Makefile

#This is an example for describing makefile

add:main.o add.o
    gcc -o add main.o add.o
main.o:main.c
    gcc -c main.c
add.o:add.c
    gcc -c add.c

clean:
    rm main.o add.o add
在这个 Makefile 文件中,第一行第一个字符为 # ,这是一个注释行;第二行指定可执行文件 add 依赖目标文件 main.o add.o ;第三行表述了第二行的具体依赖关系,即执行命令“ gcc -o add main.o add.o” ,将目标文件链接成为可执行文件;第四行指定目标文件 main.o 依赖源文件 main.c ;第五行表述第四行的具体依赖关系,即执行命令“ gcc -c main.c” ,将源文件 main.c 编译成目标文件 main.o ;第六行指定目标文件 add.o 依赖源文件 add.c ;第七行表述第六行的具体依赖关系,即执行命令“ gcc -c add.c” ,将源文件 add.c 编译成目标文件 add.o


在当前目录下输入make命令,系统将自动完成如下操作。

(1)make工具在当前目录下依次寻找名为GNUmakefilemakefileMakefile的文件,找到一个则停止查找;

(2)如果找到,它会查找文件中的第一个目标,如上面例子中的add,并将这个文件作为最后一步生成的目标;

(3)如果add文件不存在,或是add所依赖的后面的“.o”文件的修改时间比add文件晚,那么系统就会执行后面所定义的命令来生成这个add文件;

(4)如果add所依赖的“.o”文件也不存在,那么make工具就会在当前文件中查找目标为“.o”文件依赖性,如果找到则根据相应的规则生成“.o”文件;

(5)如果makefile文件中列出的源文件都存在,make工具就会先生成“.o”文件,然后再用“.o”文件链接成可执行文件,否则将提示“找不到目标”错误。

make 会一层一层地去解析文件的依赖关系,一步步地编译出各个目标,直到最终编译出第一个目标文件。在解析的过程中,如果出现错误,比如最后被依赖的文件没有找到, make 工具就会直接退出并报错。而对于每条依赖后面所定义的命令的错误, make 工具不会检查。

通常,makefile文件中还定义有clean目标,这是一个伪目标,可用来清除编译过程中生成的中间文件,例如清除上例中的内容:

clean:

     rm main.o add.o add

在上述 makefile 文件中, clean 没有被第一个目标 add 直接或间接依赖,那么它后面所定义的命令就不会被自动执行。不过,可以在 make 命令后跟 clean 目标作为参数来执行其后所定义的命令,即执行“ make clean” 命令,用于清除所有 make 过程中生成的目标,以便重新编译。

3make的语法及参数选项

make命令主要有标志、宏定义和目标名三个可选参数。其标准形式为:

make[标志][宏定义][目标名]

主要标志选项及其含义如下表所示。

  • -f FILE:读取FILE文件作为一个makefile.
  • -i:忽略命令执行返回的出错信息。
  • -s:沉默模式,在执行之前不输出相应的命令行信息。
  • -r:禁用内置隐含规则。
  • -n:非执行模式,输出所有执行命令,但并不执行。
  • -t:使用touch命令创建目标而不是根据依赖后面定义的命令来生成目标。
  • -q:根据目标文件是否已经更新返回0或非0的状态信息。
  • -p:输出所有宏定义和目标文件描述。
  • -dDebug模式,输出有关文件和检测时间的详细信息。
  • -Cdir:在所有操作前切换到dir目录。
  • -Idir:包含其他makefile文件时,利用该选项指定搜索目录。
  • -h:打印帮助信息。
  • -w:在处理makefile之前和之后,都显示makefile所在目录。

宏定义选项主要用于为makefile中已经定义的宏变量赋值,比如在下面这个GNUmakfile文件中,宏变量CC未定义。

#This is an example for describing makefilie

CC=

add:main.o add.o
    $(CC) -o add main.o add.o
main.o:main.c
    $(CC) -c main.c
add.o:add.c
    $(CC) -c add.c

clean:
    rm main.o add.o add
执行 make 命令时,我们可以通过参数为 makefile 文件内的宏变量 CC 赋值,通过宏变量 CC 来指定不同的编译器来编译源文件,如下图所示。



更严格的写法应该为宏定义选项将上双引号,即“CC=gcc”,尤其是在宏定义的右值有空格时。

目标名选项用来指定make命令要编译或执行的目标,并且允许同时指定多个目标,如上例中的“make clean”。操作时按照从左到右的顺序依次编译或执行各个目标。如果不指定目标名选项,则系统默认指向makefile文件中的第一个目标,如上例中的add

二、makfile

1、剖析makefile

Makefilemake命令依赖并读取的配置文件,它描述了编译整个项目的详细规则,为了让make命令得以识别,makefile文件遵循一定的格式,它通常包含如下内容:

  • 需要由make命令创建的目标对象(targets),通常是目标文件或者可执行文件;
  • 要创建的目标对象所依赖的文件(dependent_files)
  • 创建每个目标对象时需要运行的命令(command)

它的格式为:

targets…:dependent_files …

(tab)command

例如,有两个文件分别为 hello.c hello.h ,创建的目标体为 hello.o ,执行的命令为 gcc 编译指令“ gcc–c hello.c” ,那么对应的 Makefile 就可以写为:

#This is an example for describing makefile

hello.o:hello.c hello.h //要创建的目标对象所偏依赖的文件

     gcc -c hello.c -o hello.o //创建目标对象要运行的命令

注意,在 Makefile 中的每个 command 前必须有制表符 tab ,否则在运行 make 命令时会出错。在 Makefile 文件所在目录执行 make 命令,使用 make 命令的格式为: make target ,这样 make 命令就会自动解析 Makefile 文件并搜寻指定 target 后面的依赖文件 dependent_files ,当且仅当依赖文件都存在时才执行后面的 command 语句,否则需要先生成依赖文件方可。

2、说说makefile的文件名

默认情况下,make命令会在当前目录下按顺序寻找文件名为GNUMakefilemakefileMakefile的文件,倘若找到一个则停止往后继续寻找,如果都未找到,则提示类似于“没有指明目标并且找不到makefile”的错误。在这个三个文件名中,最好是用Makefile这个文件名,因为这个名字首字母大写比较醒目,最好不要使用GNUMakefile作为文件名,因为它只被GNUmake识别,还有一些make只对首字母小写的makefile文件敏感,但是通常来说,大多数的make都支持Maklefilemakefile这两个默认文件名。

我们也可以使用别的名字来命名makefile文件,比如“make.mips”、“make.arm”、“make.android”等,为了让make命令可以识别这些特殊的文件名,在执行make命令时需要加上“-f”参数,如:make -f make.mips

3makefile的包含

C/C++的“#include”一样,makefile文件可以使用include关键字将其他makefile包含进来,被包含的文件内容会原封不动地加载到当前makefile文件的包含位置。include关键词前面可以有一些空格字符,但是绝对不能以制表符tab键,因为制表符在makefile文件内通常用来标识一个命令的开始。include关键词后面指定要包含的文件名、路径或者变量,它们之间用一个或者多个空格隔开,举例来说,倘若你有几个makefilea.makeb.makec.make,还有一个makefile相关文件(定义一些宏变量或者宏变量的文件)make.rules,以及一个变量$(include_dir),那么下面语句:

include_dir:=/usr/include

include*.make $(include_dir)make.rules

等价于:

include a.make b.make c.make /usr/include/make.rules

make命令执行时,先将include关键字后面指定的相关文件内容加载进来并安置在当前位置,如果文件未指定绝对路径,make命令将先在当前目录寻找,倘若未找到,make命令一般还会去下面几个目录寻找:

(1)如果执行make命令时使用“-I”参数指定了某个目录,make命令会去该目下寻找相关文件;

(2)如果/usr/local/bin/usr/include目录存在,make命令将去该目录下寻找相关文件。

make命令如果未找到include关键字指定的某个文件,将警告“没有那个文件或目录”等错误而停止运行,如果想让make命令不去理会这些相关文件是否存在而继续执行,可以在include前面加上一个减号“-”,它表示无论include加载过程中出现的错误都被忽略。


相关文章:

  • Ubuntu下进行Android开发的相关配置
  • 如何阅读不同格式的Ubuntu/Linux帮助文档
  • Ubuntu 中其他编程语言的使用
  • Ubuntu中如何修复GRUB2 Boot Loader
  • Ubuntu下的虚拟化KVM
  • 使用MeTriX MuX 1.1
  • 使用Endnote进行文献管理
  • GOOGLE学术检索技巧
  • MATLAB下的程序调试
  • SVM学习笔记(1)LIBSVM在matlab下的使用安装
  • SVM学习笔记(2)LIBSVM在python下的使用
  • SVM学习笔记(3)LIBSVM中的核函数及其参数
  • C++一个完整的类实例及其调用
  • LaTeX算法排版例子
  • 高斯卷积
  • 【css3】浏览器内核及其兼容性
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • Android Studio:GIT提交项目到远程仓库
  • Android组件 - 收藏集 - 掘金
  • ES2017异步函数现已正式可用
  • isset在php5.6-和php7.0+的一些差异
  • JavaScript标准库系列——Math对象和Date对象(二)
  • JDK9: 集成 Jshell 和 Maven 项目.
  • Linux CTF 逆向入门
  • React组件设计模式(一)
  • spring + angular 实现导出excel
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 翻译:Hystrix - How To Use
  • 利用DataURL技术在网页上显示图片
  • 数据仓库的几种建模方法
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 用 Swift 编写面向协议的视图
  • 由插件封装引出的一丢丢思考
  • ​一些不规范的GTID使用场景
  • !!Dom4j 学习笔记
  • # Java NIO(一)FileChannel
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #DBA杂记1
  • ( 10 )MySQL中的外键
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (定时器/计数器)中断系统(详解与使用)
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (算法)Game
  • (一)基于IDEA的JAVA基础10
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (转)C#调用WebService 基础
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .Net 代码性能 - (1)
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET程序员迈向卓越的必由之路