前言 

在学习完了Linux的基本操作之后,我们知道在linux中编写代码,编译代码都是要手动gcc命令,来执行这串代码的。

 但是我们难道在以后运行代码的时候,难道都要自己敲gcc命令嘛?这是不是有点太烦了?

 在vs中,我们编写好代码之后,直接点击构建项目,就会直接帮我们自动化构建好了,我们在linux中构建的时候,有的时候上百个文件,还是比较麻烦的,所以到底有没有一些简单的做法呢?当然是有的啦~

这个工具呢就是Makefile/make项目自动化构建工具。

会不会写Makefile,从一个侧面说明了一个人是否具有完成大型工程的能力;一个工程的源文件不计其数,其按类型、功能、模板分别放在一个若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作;Makefile带来的好处就是——自动化编译。一旦写好,只需要一个make命令,整个工程就完成自动化编译,极大提高了软件开发的效率;make是一个命令工具,是一个解释Makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,visual C++的make,linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。make是一个命令,Makefile是一个文件,两个搭配使用,完成项目自动化构建。

实例理解

 在上图中,mytest:text.c与gcc -o mytest test.c是匹配的,clean:与rm -f mytest是匹配的。这两组中的每个第一句是依赖关系,两组的下面称为依赖方法。

Makefile实现原理

以图为例(与上图例子一致,为了方便大家查看,就在下面也放一份):

 Makefile文件写好之后,当我们实际在命令行运行make的时候,对应的make程序,会在当前目录下找这个Makefile,读取Makefile里面的内容:

make会根据Makefile里面的内容,完成编译、清理工作。根据Makefile里面的依赖关系,将依赖关系以依赖方法形成目标文件。

(如上图:编译器就会知道依赖关系test.c文件形成mytest的目标文件,那么怎么形成呢?就是根据对应的gcc -o mytest test.c这样的依赖方法)

依赖关系

那么到底如何理解依赖关系和依赖方法呢?

这里就给大家将一个小故事咯~便于大家理解:

小明是某大学里的在校生,这天他没有生活费了。他就打电话给那个人:“爸!我是你儿子!”在小明看来,你是我爸你就应该给我生活费;但是在老爹看来,你小子说这话是啥意思?你想干啥?老爹是一脸懵啊。小明挂了电话之后,就又想了想:“爸!我是你儿子!给我点生活费!”这下他老爹才懂了,原来他儿子没有生活费了,想要生活费。

所以在上面的小故事中,“我是你儿子”就是依赖关系,“给我点生活费”就是依赖方法。在日常生活中,我们也难免得需要拜托谁做一件什么事,在计算机中也是一样的,完成一件事的必然要素少不了依赖关系与依赖方法。

在上图中的依赖关系如下:

其实,我们知道程序翻译运行的过程为:预处理、编译、汇编和链接。

翻译的过程 gcc命令 说明 预处理 (进行宏替换) gcc -E test.c -o test.i -E 让gcc在预处理结束后,就停止编译 “从现在开始进行程序的翻译,预处理完成,就停下来” 宏替换 去注释 头文件展开 条件编译 生成.i文件 编译 (生成编译) gcc -S test.i -o test.s -S 只进行编译不进行汇编,生成汇编代码 “从现在开始进行程序的编译,汇编完成就停下来” 检查代码规范性 检查语法错误 (确认无误后汇编) 将C语言代码翻译成汇编语言 生成.s文件 汇编 gcc -C test.o -o test.o -C “从开始带现在进行程序的翻译”,汇编完成就停下来 将汇编语言编译成二进制目标文件 生成.o文件 链接 gcc test.o -o my.exe 形成可执行程序 生成.exe文件

所以将上面gcc -o mytest test.c指令写完整就是如下:

诶?很奇怪啊,怎么感觉顺序呢不太对?在这里我们就要说一下Makefile的工作原理了:

Makefile在执行过程中,是从上往下进行扫描的;当它看到的第一个文件时,其实并不是.c文件,第一个是.o文件;也就是识别的从上往下,第一组依赖关系;可是识别的依赖关系中的.o文件并不存在,所以它下面的命令,也就是依赖方法不能被执行,就无法形成可执行程序;所以Makefile就会自动在后续,继续再找下一组依赖关系,根据下一组依赖关系来形成.o文件;但是在形成.o文件还是需要依赖.s文件,依次类推直到遇到依赖文件存在.c文件;所以存在.c文件,就会有.i文件、.s文件以及.o文件;所以就下往上执行了,很类似与递归问题。

在上图中:

上面的文件 mytest ,它依赖 mytest.omytest.o , 它依赖 mytest.smytest.s , 它依赖 mytest.imytest.i , 它依赖 mytest.c

make、Makefile会自动根据文件中的依赖关系,进行自动推导,帮助我们执行所有相关的依赖方法。

这里注意一下,因为Makefile就是类似于一个选择器,里面包含了各种指令的选项,但是一般默认智慧运行第一个依赖关系所对应的指令,所以上面的各组依赖关系是可以乱序的,但是必须要将最重要的一条指令放在最前面,如下图:

 上面的依赖关系,是为了让大家能够理解,这里不建议大家写成这样,直接就gcc -o mytest test.c指令就可以。

依赖文件列表

依赖文件列表可以为多个文件,按照空格分割分。

mytest:依赖文件1 依赖文件2 依赖文件3 ...

 

我们刚刚也说了clean:与rm -f mytest是匹配的。根据上图我们也不难看出,clean后面是空的,也就是说clean不依赖任何文件,也就是说:

依赖文件列表可以为空。 

依赖方法

依赖方法的指令前必须打一个[Tab]键,按四下空格会报错。

多条依赖方法

依赖方法不限于一条,可以是多条的。 

项目清理

.PHONY含义

在Makefile文件执行的时候,如下图,我们发现在使用make的时候默认执行的是Makefile文件中的第一对的依赖关系和依赖方法:

Makefile默认形成第一个可执行文件。

 其次,我们看下边运行过程:

make: `mytest' is up to date. :当前可执行程序是最新的。

所以Makefile会在源代码的内容没有修改,没有变化时,Makefile默认就会拦截再一次的make命令。

那么如果今天我们就想让其一直执行,不要拦截,即使在没有被修改源码的情况下,也要让make指令一直执行,那么怎么做呢?只需要:

.PHONY : XXX XXX对应的方法总是要被执行的。

 当然我们理解了原理之后,小编还是建议大家之后再清理的命令加上总是可执行即可。所以我们目前标准的就是以下5行:

以后我们需要的Makefile文件

通配符认识

符号含义$^所有依赖文件列表$@所有目标文件$<所有依赖文件的第一个文件

 在这里,我们可以将$,理解为取内容。

如下图,gcc在编译的过程,Makefile会自动进行符号替换:

把对应的$@就会自动替换为目标文件;把对应的$^就会自动替换为所有依赖文件。

变量

Makefile也是支持变量的。但是并不是之前学习的编程语言中的整形、浮点型int/double=1/1.0等等。Makefile是解释性的,所以它的变量直接就是符号,比如说,“‘形成的可执行程序’为‘XX’”:

bin是“目标文件可执行程序”的变量 ;src是“依赖文件列表”的变量;$可以理解为去内容;$(bin):取出bin变量中的内容,即目标文件;$(src):取出src变量中的内容,即依赖文件列表;$@:所有的目标文件;$^:所有的依赖文件列表。

在执行make的时候,Makefile就会进行变量替换,可以理解成为宏的替换,也可以执行程序。

将来你可能会有很多地方遇到使用make的项目与文件,那就只需要将变量的内容修改,就可以完成不同文件的make指令进行执行,就会很简单方便。

make指令打印隐藏

在命令行输入make指令之后,系统都会自己打印出我们底层所输入的依赖方法对应的指令,那么如何将make指令进行隐藏也就是让它不再打印呢?

直接在命令前面加一个“@”符号:

推荐使用的Makefile模板

bin=test.exe

src=test.c

$(bin):$(src)

@gcc -o $@ $^

@echo "compiler $(src) to $(bin)..."

.PHONY:clean

clean:

@rm -f $(bin)

@echo "clean project..."

其中echo是打印字符串。 

如下图就会有提示信息:

必熟知知识

为什么Makefile对最新的可执行程序,默认不想重新形成?

今天,我们编译的代码只有一个源文件,以后我们编译的源代码是两千个源文件,成百上千个源文件,在我们修改bug的时候,可能只是修改几行代码,做完改动之后,如果我们要将所有的源文件重新编译一遍,那么就是效率很低,又或者说,如果我们没有修改,又make的话,那么编译器又会重新编译,假如一个文件需要用0.1秒,那么成百上千个代码就需要十几分钟甚至几个小时,所以效率是极其低的。

所以在Makefile为了调高效率,就默认对最新的可执行程序,不重新形成。

Makefile怎么知道程序需要被编译的呢?

ACM时间是Linux系统下分别代表Access,Modify和Change三个时间。

Access:文件最近被访问的时间(访问)Modify : 文件内容最近被修改的时间(修改)Change : 文件属性最近被修改的时间(改变)

当然我们要知道的是:

源代码与可执行程序的文件内容最近被修改的时间(修改)一定是不同的。 

因为我们基本先写源代码,再进行编译;所以可执行程序一般是比源代码的时间更新的。如果修改的源代码,那么此时源代码比可执行程序的时间更新,所以就需要重新编译。

Makefile怎么知道程序需要被编译的呢?

对比可执行程序的最近修改时间和源文件最近的修改时间,谁更新。如果可执行程序更新,就不需要重新编译了;反之,源文件更新,就需要重新编译。

所以我们在用vs写代码的时候,出现错误 ,再修改了很多次,仍旧报这个错误,这个时候,可以重新清理一下,重新构建一下,重新生成解决方案,这个问题就会可能解决。

好文链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: