简体   繁体   English

分别创建目标文件然后在Makefile中将它们链接在一起的目的是什么?

[英]What is the purpose of creating object files separately and then linking them together in a Makefile?

When using the gcc compiler, it will link and compile in one step. 使用gcc编译器时,它将一步链接和编译。 However, it appears to be idiomatic to turn source files into object files and then link them at the end. 但是,将源文件转换为目标文件然后在最后链接它们似乎是惯用的。 To me, this seems unnecessary. 对我来说,这似乎是不必要的。 Not only does this clutter up your directories with a bunch of object files, but it complicates the Makefile when you can simply tack on all the source files to your compiler. 这不仅会使您的目录与一堆目标文件混乱,而且当您可以简单地将所有源文件添加到编译器时,它会使Makefile复杂化。 For example, here's what I consider to be simple: 例如,我认为这很简单:

.PHONY: all

SOURCES = $(wildcard *.cpp)

all: default
default:
    g++ $(SOURCES) -o test

Which neatly becomes: 整齐地成为:

g++ main.cpp test.cpp -o test

However, more complicated Makefiles which use pattern rules would clutter the output for each and every file. 但是,使用模式规则的更复杂的Makefile会使每个文件的输出混乱。 For example: 例如:

.PHONY: all

SOURCES = $(wildcard *.cpp)
OBJECTS = $(SOURCES:.cpp=.o)

%.o: %.cpp
    g++ -c -o $@ $<

all: default
default: $(OBJECTS)
    g++ -o test $^

clean:
    rm -rf *.o

g++ -c -o main.o main.cpp
g++ -c -o test.o test.cpp
g++ -o test main.o test.o

To me, this seems unnecessary complicated and error prone. 对我来说,这似乎是不必要的复杂和容易出错。 So what are the reason(s) for this practice? 那么这种做法的原因是什么?

Why do you want to write a Makefile and not write a simple shell script? 为什么要编写Makefile而不是编写简单的shell脚本? In the example that you consider simple, you make no use of any feature of make, you could even write a simple shell script that understands the keywords build and clean , and that's it! 在您认为简单的示例中,您不使用make的任何功能,您甚至可以编写一个简单的shell脚本来理解关键字buildclean ,就是这样!

You are actually questioning about the point of writing Makefiles instead of shell scripts, and I will address this in my answer. 你实际上是在质疑Makefiles而不是shell脚本的问题,我将在我的回答中解决这个问题。

Also note that in the simple case where we compile and link three moderately sized files, any approach is likely to be satisfying. 另请注意,在我们编译和链接三个中等大小的文件的简单情况下,任何方法都可能令人满意。 I will therefore consider the general case but many benefits of using Makefiles are only important on larger projects. 因此,我将考虑一般情况,但使用Makefile的许多好处只对大型项目很重要。 Once we learned the best tool which allows us to master complicated cases, we want to use it in simple cases as well. 一旦我们学会了使我们能够掌握复杂案例的最佳工具,我们也希望在简单的情况下使用它。

The procedural paradigm of shell scripts is wrong for compilation-like jobs shell脚本的过程范例对于类似编译的作业是错误的

Writing a Makefile is similar to writing a shell script with a slight change of perspective. 编写Makefile类似于编写一个稍微改变透视的shell脚本。 In a shell script, we describe a procedural solution to a problem: we can start to describe the whole procedure in very abstract terms using undefined functions, and we refine this description until we reached the most elementary level of description, where a procedure is just a plain shell command. 在shell脚本中,我们描述了一个问题的过程解决方案:我们可以使用未定义的函数以非常抽象的术语开始描述整个过程,并且我们会对这个描述进行优化,直到我们达到最基本的描述级别,其中一个过程就是一个普通的shell命令。 In a Makefile, we do not introduce any abstraction, but we focus on the files we want to produce and how we can produce them. 在Makefile中,我们不介绍任何抽象,但我们关注的是我们想要生成的文件以及如何生成它们。 This works well because in UNIX, everything is a file, therefore each treatment is accomplished by a program which reads its input data from input files , do some computation and write the results in some output files . 这很有效,因为在UNIX中,一切都是文件,因此每个处理都是由一个程序完成的,该程序输入文件中读取输入数据,进行一些计算并将结果写入一些输出文件

If we want to compute something complicated, we have to use a lot of input files which are treated by programs whose outputs are used as inputs to other programs, and so on until we have produced our final files containing our result. 如果我们想要计算复杂的东西,我们必须使用很多输入文件,这些输入文件由程序处理,其输出用作其他程序的输入,依此类推,直到我们生成包含结果的最终文件为止。 If we translate the plan to prepare our final file into a bunch of procedures in a shell script, then the current state of the processing is made implicit : the plan executor knows “where it is at” because it is executing a given procedure, which implicitly guarantees that such and such computations were already done, that is, that such and such intermediary files were already prepared. 如果我们将计划转换为将最终文件准备到shell脚本中的一系列过程中,那么处理的当前状态是隐式的 :计划执行器知道“它在哪里”,因为它正在执行给定的过程,隐含地保证已经完成了这样的和这样的计算,也就是说,已经准备好了这样的和这样的中间文件。 Now, which data describes “where the plan executor is at” ? 现在,哪些数据描述了“计划执行者所在的位置”?

Innocuous observation The data which describes “where the plan executor is at” is precisely the set of intermediary files which were already prepared, and this is exactly the data which is made explicit when we write Makefiles. 无害的观察 描述“计划执行者所在的位置”的数据正是已经准备好的中间文件集,这正是我们编写Makefile时明确的数据。

This innocuous observation is actually the conceptual difference between shell scripts and Makefiles which explains all the advantages of Makefiles over shell scripts in compilation jobs and similar jobs. 这种无害的观察实际上是shell脚本和Makefile之间的概念差异,它解释了Makefile在编译作业和类似作业中相对于shell脚本的所有优点。 Of course, to fully appreciate these advantages, we have to write correct Makefiles, which might be hard for beginners. 当然,要充分理解这些优点,我们必须编写正确的 Makefile,这对初学者来说可能很难。

Make makes it easy to continue an interrupted task where it was at Make可以轻松地继续执行中断任务

When we describe a compilation job with a Makefile, we can easily interrupt it and resume it later. 当我们使用Makefile描述编译作业时,我们可以轻松地中断它并在以后恢复它。 This is a consequence of the innocuous observation . 这是无害观察的结果 A similar effect can only be achieved with considerable efforts in a shell script. 只有在shell脚本中付出相当大的努力才能实现类似的效果。

Make makes it easy to work with several builds of a project Make使得使用项目的多个版本变得容易

You observed that Makefiles will clutter the source tree with object files. 您发现Makefile会使源文件与对象文件混乱。 But Makefiles can actually be parametrised to store these object files in a dedicated directory, and advanced Makefiles allow us to have simultaneously several directories containing several builds of a project with distinct options. 但Makefiles实际上可以进行参数化以将这些目标文件存储在专用目录中,而高级Makefile允许我们同时拥有包含具有不同选项的项目的多个构建的多个目录。 (For instance, with distinct features enabled, or debug versions, etc.) This is also consequence of the innocuous observation that Makefiles are actually articulated around the set of intermediary files. (例如,启用了不同的功能或调试版本等)这也是无害的观察结果 ,即Makefiles实际上围绕着一组中间文件进行了表达。

Make makes it easy to parallelise builds Make使得构建并行化变得容易

We can easily build a program in parallel since this is a standard function of many versions of make . 我们可以轻松地并行构建程序,因为这是许多版本的make的标准函数。 This is also consequence of the innocuous observation : because “where the plan executor is at” is an explicit data in a Makefile, it is possible for make to reason about it. 这也是无害观察的结果 :因为“计划执行者所在的位置”是Makefile中的显式数据,因此make可能会对其make推理。 Achieving a similar effect in a shell script would require a great effort. 在shell脚本中实现类似的效果需要付出很大的努力。

Makefiles are easily extensible Makefile易于扩展

Because of the special perspective — that is, as another consequence of the innocuous observation — used to write Makefiles, we can easily extend them. 由于特殊的观点 - 也就是说,作为无害观察的另一个结果 - 用于编写Makefile,我们可以轻松地扩展它们。 For instance, if we decide that all our database I/O boilerplate code should be written by an automatic tool, we just have to write in the Makefile which files should the automatic tool use as inputs to write the boilerplate code. 例如,如果我们决定所有数据库I / O样板代码都应该由自动工具编写,我们只需要在Makefile中写入自动工具用作编写样板代码的输入的文件。 Nothing less, nothing more. 没有更少,仅此而已。 And we can add this description pretty much where we like, make will get it anyway. 我们几乎可以在那里我们喜欢,添加此描述make会得到也无妨。 Doing such an extension in a shell script build would be harder than necessary. 在shell脚本构建中执行此类扩展将比必要更难。

This extensibility ease is a great incentive for Makefile code reuse. 这种可扩展性的易用性是Makefile代码重用的一个很大的动力。

The two big reasons at the top of a list of many reasons for me are: 对我来说,众多原因列表中最重要的两个原因是:

  • You can compile multiple source files at the same time decreasing build time 您可以同时编译多个源文件,从而减少构建时间
  • If you change one file, you only recompile that one file instead of recompiling everything 如果更改了一个文件,则只重新编译该文件而不是重新编译所有文件

Well, the argument for "compile everything every time" can be seen here: 好吧,这里可以看到“每次编译所有内容”的论点:

http://xkcd.com/303/ http://xkcd.com/303/

but joking aside, it's MUCH faster to compile one file when you have made a small change, compared to recompiling everything every time. 但是开玩笑说,与每次重新编译所有内容相比,编译一个文件时进行小的更改要快得多。 My Pascal compiler project is not very large, but it still takes about 35 seconds or so to compile it. 我的Pascal编译器项目不是很大,但编译它仍然需要大约35秒左右。

Using make -j3 (that is runing 3 compile jobs at once, I'm currently on my spare computer with only a dual core processor), the time to compile the files take only ten seconds less, but you can't do -j 3 if you don't have multiple compile jobs. 使用make -j3 (一次运行3个编译作业,我目前在备用计算机上只有一个双核处理器),编译文件的时间只需少了十秒,但你不能做-j 3如果您没有多个编译作业。

Recompiling only one (of the larger) modules takes 16 seconds. 仅重新编译一个(较大的)模块需要16秒。

I know what I'd rather wait for of 16 or 35 seconds... 我知道我宁愿等待16或35秒......

I will explain why making object files leads to faster compilation. 我将解释为什么制作目标文件可以加快编译速度。

Consider the following analogy : 请考虑以下类比:

You are learning to build a car from scratch (ie you have lots of metal and rubber for the wheels). 你正在学习从头开始制造一辆汽车(即你的车轮上有很多金属和橡胶)。 You have all the machines required to make all the major parts of the car (the frame, the wheels, the engine etc.). 你拥有制造汽车所有主要部件所需的所有机器(车架,车轮,发动机等)。 Assume each machine builds one particular part of the car, for eg say you have a frame-building machine . 假设每台机器构建汽车的一个特定部分,例如假设您有一个框架构建机器 Assume it takes significant amount of time to ready a machine because you have to read a very complicated manual :( 假设需要时间显著量来准备一台机器,因为你要读一个非常复杂的手动:(

Scenario 1 You spend half a day trying to ready all the machines and then one hour to build parts and solder together all the parts to finish making the car. 场景1您花了半天时间尝试准备好所有机器,然后花一个小时来制造零件并将所有零件焊接在一起以完成制造汽车。 Then you turn off all the machines you had used (which removed all your custom settings). 然后关闭所有使用过的机器(删除了所有自定义设置)。 However, later on you realize that you made the wrong engine for the car. 但是,后来你意识到你为汽车制造了错误的发动机。 Since you had soldered together all the parts of the car, you cannot replace the previous engine, so you have to make all the parts again. 由于您已将汽车的所有部件焊接在一起,因此您无法更换以前的发动机,因此您必须再次制造所有部件。 You spend another half of a day to get the machines ready(by rereading the manual) and another hour to make all the parts and join them. 你花了一半的时间来准备机器(通过重读手册)和另一个小时来制作所有部件并加入它们。 Painful ! 痛苦!

Scenario 2 You get the machines ready in half a day and then you note down everything you had done while readying the machines in a notebook . 场景2您可以在半天内准备好机器,然后记下您在笔记本电脑中准备机器时所做的一切 You make all the parts of the car in an hour and you solder together all the parts to finish making the car and turn off the machines. 您可以在一小时内完成汽车的所有部件,并将所有部件焊接在一起,以完成制造汽车并关闭机器。 However, later on you realize that you put made the wrong engine for the car. 但是,后来你意识到你为汽车制造了错误的发动机。 You have to make all parts again. 你必须重新制作所有部件。 Because you had kept track of everything you had done in a notebook getting the machines ready now only take 10 mins . 因为你已经记录了你在笔记本电脑上所做的一切,现在只需要10分钟 You again spend an hour to make the parts and join them together. 你再花一个小时制作零件并将它们连接在一起。 This saved you a lot of time (almost half a day). 这为您节省了大量时间(差不多半天)。

  • The complicated manual you read is the source file . 您阅读的复杂手册是源文件
  • The notebook with your notes is the object file . 带有笔记的笔记本是目标文件
  • The final car is the binary file. 最后一辆车是二进制文件。

The object files are intermediate results that help you avoid doing all the compilation (getting the machines ready) again by keeping track of most of the hard work that you have already done in an appropriate form before making the binary file. 目标文件是中间结果,通过在制作二进制文件之前跟踪已经以适当形式完成的大部分艰苦工作,帮助您避免再次进行所有编译(使计算机准备好)。 Object files have more purpose! 对象文件有更多用途! You should read about them if this excites you :D. 如果激动你,你应该阅读它们:D。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM