
👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:Linux
文章目录
如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

上篇博客,我们学习了 gcc 编译器。学会了如何在 Linux 上编译 C语言 代码。
对于我们平常练习是没问题的,但是如果有上百个源文件,该怎么办?难道还是一个个都用 gcc 编译为 .o 文件,最后将它们一起链接起来?
这肯定是不实际的,这使得编译成为了一个很麻烦的事情。
之前我们在 vs 中写代码时,使用快捷键就可以很快地进行程序的编译,或者直接执行程序,那么在 Linux 下能否也能实现这个功能?
能否减少编译代码时的风险,使编译更加快捷,一定程度实现自动化编译?
当然有,这就是我们 今天的目标之一:使用 make/makefile 构建一个简单的自动化工具。
另外 a n d u i n anduin anduin 还会讲解 make/makefile 的概念、原理和规则,从多层面理解透彻它们。
makefile :
makefile 是一个文件。它是一个工程文件的编译规则,描述了整个工程的编译链接等规则。
好的 makefile 文件可以使用一行命令来完成 “自动化编译” ,一旦写好 makefile ,就只要使用在 shell 提示符下输入 make 命令,从而完成对工程的编译,极大提高效率。
make :
make 是一个命令工具,用来一个解释 makefile 中文件中的指令。
当已经编写好 makefile 文件后,只需要使用 make ,就可以执行 makefile 中的内容。
会不会写makefile,也从侧面说明了一个程序员是否具备完成大型工程的能力。
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。所以使用好 make/makefile ,可以使得开发更加得心应手。
一句话总结:make 是一条命令,makefile 是一个文件,两个搭配使用,完成项目自动化构建。
在讲解 make/makefile 之前,我们先写一个小 demo ,以这个 demo 为基准,对其进行讲解。
makefile 文件需要创建在当前工程的目录下,makefile 文件的名称可以为 makefile 或 Makefile 。
假设当前工程下已经有了一个 test.c ,我们直接开始 demo 的编写:

这样 makefile 就编写好了,就两句话,这时编写的 makefile 可以完成对程序的编译。
我们返回终端,使用 make 就可以对 test.c 进行编译:

使用 make 指令后,makefile 的第二行内容被打印在终端,并且生成了可执行程序 test ,test 程序也是可以执行的。
上面写了一个小 demo ,那么这个 demo 实现的原理我们还不清楚,所以接下来 a n d u i n anduin anduin 来讲一下这中间的原理和规则。
在 myfile 文件中,有这样一句话:
test:test.c
刚刚测试过我们知道 test 是目标文件,而 test.c 则是原始文件。
而 test.c 经过 gcc test.c -o test 生成 test 文件。
它们之间的关系 :
gcc test.c -o test 指令,这条指令就是 依赖方法 。那么 依赖关系 和 依赖方法 如何理解呢?我来举个例子来帮助大家 感性理解 :
例子1:
假设你在上大学,到月底了,你的生活费没了。那么这时你就打电话给你的老爹,来一个亲切的问候(要生活费)。
当你拨通电话,你说:“爸,我是你儿子!” 然后把电话挂掉。
这种情况能要到生活费吗?肯定不行,因为你只是向你的老爹 表明了你是谁(依赖关系) ,但是并没有告诉他 你要干什么(依赖方法) ,所以你的老爹一般是不会想着给你打钱,而是担心你的安全。
所以,只有依赖关系没用,还需要有依赖方法,依赖关系和依赖方法需要同时具备。
例子2 :
如果你在上大学,马上要考试了。你决定又给你的老爹打电话,说:“爸,我是你儿子!能不能帮我期中考?” 听完这句话,你的老爹思考了很久。于是决定连夜赶到你的学校,并请你体验了一顿 “七匹狼” 。
综上我们可以发现,你有依赖关系(表明了身份) ,你也有依赖方法(叫你老爹考试),但是 依赖方法错误 ,也没有达到目的,反而造成错误结果。
这就是典型的有正确的依赖关系但是依赖方法错误。
所以,正确的依赖关系必须具有正确的依赖方法。
例子3 :
如果你有一天,又到了月底?你随意拨通了一串号码,和陌生人说:"爸,我是你儿子!我没生活费了,给我打钱。"再以迅雷不及掩耳之势挂掉了电话。电话那头的人很懵。于是,当天晚上,你的梦里一直出现"功德-1,功德-1 … " 。
这个例子说明,你有了 正确的依赖方法(要生活费) ,但是你 没有正确的依赖关系(陌生人) ,这也办不成事。
所以,正确的依赖方法也需要正确的依赖关系。
通过以上三个例子,我们得出结论,依赖关系和依赖方法必须同时具备并正确,缺一不可 。
我们基于上层的感性理解,再通过一个 makefile 深层理解一下:

(实际上我们编译代码并不需要进行,这里只是为了理解 … ,平常就写成 demo 那样就可以)
在 makefile 文件中,共有四组依赖关系和依赖方法 ,当使用 make 调用 makefile 文件中内容时,便开始执行 makefile 中的内容:
我们发现,这一过程就像 数据结构的栈 。
当目标文件所依赖的文件不存在时,就会将依赖方法入栈,知道依赖关系匹配了,再执行相应的依赖方法,在按照栈的规则,逐渐将栈中的元素出栈,规则满足后进先出。
为了验证这些步骤是否都被执行,我们 make 一下看看:

依赖方法对应的文件都产生了,这也说明我们讲解的步骤是正确无误的。
平时写代码时,经常需要反复编译,执行代码。
而在下一次重新编译之前,需要清理一下上次生成的可执行程序。但是清理的时候可能清理错误,不小心把源文件删了,这时又造成了问题。
而上面的步骤,我们也生成了很多附加文件(如 test.i 等)。
所以我们基于 demo 增加一个清理功能:

使用 make 测试一下:

文件也都删除了。
这是为什么,新增语句是什么意思?继续讲解原理。
.PHONY 修饰的对象是伪目标,伪目标的特性是:总是被执行的。
.PHONY 修饰的一定能被反复执行,但是能被反复执行的不一定被 .PHONY 修饰。
多次执行 make 和 make clean 试试:

(注:makefile 默认从上到下扫描只会执行第一组的依赖关系和依赖方法,所以默认执行第一组,这时使用 make 就可以;而 clean 为第二组,所以需要 make clean ,加上对应的关系。同理,对于第一组,使用 make test 也能执行。)
发现,第一组关系没有被 .PHONY 修饰,而不能重复执行。但是第二组 clean 可以重复执行 。
但是怎么证明 .PHONY 修饰对象之后,对象能被反复执行?口说无凭,所以,我们再验证一下:
给 test 加上修饰:


加上 .PHONY 修饰后,make 可以执行多次了,证明了 .PHONY 的作用。
但是能被反复执行的不一定被 .PHONY 修饰,就比如 clean :


当 clean 去掉修饰之后,依然能被反复执行。
一般对于编译来说,是不加 .PHONY 修饰的。
因为编译是十分耗时间,特别是当工程量很大的时候,编译一两小时都不为过。所以防止对未修改的程序反复编译 ,一般编译时不加修饰。
但是 清理clean 是可以多次执行的,因为删除不太浪费时间,且可以反复清理,确认是否清理完毕。并且为了肯定清理可以被多次执行,所以通常用 .PHONY 修饰。
上面我们测试 make 时,发现当编译过一次后,继续使用 make 就无法继续编译了。但是 clean 是可以不加修饰反复执行的。原因我们也探讨过,但是 make 是如何确定是否要编译?
是这样的,对于程序来说,时间有两条线。第一条是源代码时间的一条线,第二条是形成的可执行程序的时间的一条线 。
而对于它们之间的次序,是先有源代码,再有可执行程序。
所以只要可执行程序的最近修改时间比源文件的修改时间晚,就认为当前可执行程序是最新的,为了减少时间和其他开销,于是不执行编译;否则执行编译 。
我们再重新生成可执行程序,并重复 make ,观察它们的时间:
观察时间,这里就要用到 stat 指令,它的 modify 就是最近修改时间 ,如果对 stat 指令不了解的小伙伴可以看这一篇:基本指令(一) (在 ls 指令部分)。

可执行程序 test 的时间明显比 源代码 test.c 晚,所以 make 并不能起作用。
那么基于对这个概念的理解,我们能否钻空子来 欺骗一下 make ?
补充:
touch 指令为创建一个文件。若文件不存在则会创建一个文件;若文件存在则会把文件时间更新到最新。
使用 touch 更新一下 test.c 的时间,用 stat 观察时间,并反复 make 试试:


由此,我们发现可以使用 touch 来 “欺骗” make 来反复编译。这也侧面证明了: make 对于是否编译的决策是基于修改时间,而并不是基于文件内容是否修改 。
test:test.c
gcc test.c -o test
.PHONY:clean
clean:
rm -f test.i test.s test.o test
tab ,为固定格式: 右边可以有多个依赖文件 ,: 右边通常被称为依赖文件列表: 右边,目标文件对应的依赖文件列表可以为空 (例如 clean)到这里本篇博客就到此结束了。
实际开发中,好的 make/makefile 可以让项目开发事半功倍。所以构建自动化工具还是蛮重要的,可以大大提高开发的效率。
但是博主能力有限,对于博主当前,构建这么一个简单的工具就足够了。我们今天的重点是放在 make/makefile 的规则上。
感兴趣的小伙伴也可以往 makefile 中添砖加瓦,构建出自己开发的利器。
如果觉得 a n d u i n anduin anduin 写的不错的话,可以 点赞 + 收藏 + 评论 支持一下哦!我们下期见~
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我知道您通常应该在Rails中使用新建/创建和编辑/更新之间的链接,但我有一个情况需要其他东西。无论如何我可以实现同样的连接吗?我有一个模型表单,我希望它发布数据(类似于新View如何发布到创建操作)。这是我的表格prohibitedthisjobfrombeingsaved: 最佳答案 使用:url选项。=form_for@job,:url=>company_path,:html=>{:method=>:post/:put} 关于ruby-on-rails-rails:Howtomak
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.