Loading... (此[OOP笔记系列][1]并不会系统地记录知识点。只记录个人认为散乱,边角或者容易遗忘、需要注释的。) 本篇主要内容:一个`Makefile`模板;**对目标、伪目标的理解**。 # 1 编译 有关编译链接的全过程参见:[编译和链接的过程](https://blog.csdn.net/guaiguaihenguai/article/details/81160310) 为防止丢失粘上关键的流程图: ![流程.png][2] 从源代码生成目标文件: ```shell g++ -std=c++14 -O2 main.cpp -c -o main.o ``` `-std=c++14`指定了C++版本,`-O2`开启O2优化,`-c`表示执行完汇编就停止不进行链接。`-o`指定输出文件的名称。最后生成了`main.o`。 # 2 链接 将编译好的目标文件(各个模块)连接为最终结果: ```shell g++ -o main main.o ``` 这样就生成了可执行文件`main` # 3 Makefile 详解参见:[C++之makefile写法](https://blog.csdn.net/zong596568821xp/article/details/81134406) ## 3.1 例子 这里给出一个满足简单需求的通用Makefile: ```makefile #################################### # Learnt from the Internet # Edited by Colin # 2020.02 #################################### cc = g++ CXXFLAGS = -std=c++14 -O2 # 编译选项 prom = main # 目标文件(名称) deps = $(shell find . -name "*.h") # dependences src = $(shell find . -name "*.cpp") # sources obj = $(src:%.cpp=%.o) # objects $(prom): $(obj) # prom: target file, obj: 依赖关系表,这里是.o文件 $(cc) -o $(prom) $(obj) %.o: %.cpp $(deps) $(cc) $(CPPFLAGS) -c $< -o $@ # %<: 表示依赖目标, $@: 表示目标集合 .PHONY: clean # 指定clean为伪目标 clean: rm -rf $(prom) $(obj) ``` 在实际使用中,上述代码中的`$(shell find . -name "*.h")`会查找当前目录下所有的`.h`文件,如果有子目录会递归地往下找。而有时候我们不希望它递归下去,或者递归到某一层就停止,因此可以指定查找深度。此外,除了CXXFLAGS这个编译选项,我们还可以添加一个FLAG参数,如果将它设置为`-g`,就可以编译出用于调试的可执行文件。即在make时指定参数:`make FLAG=-g`,这一操作可以用于配置VSCode的调试功能。 做出改动的代码如下: ```makefile #################################### # Learnt from Internet # Edited by Colin # 2020.02 #################################### cc = g++ FLAG = CXXFLAGS = -O2 --std=c++17 prom = main deps = $(shell find . -maxdepth 1 -name "*.h") src = $(shell find . -maxdepth 1 -name "*.cpp") obj = $(src:%.cpp=%.o) $(prom): $(obj) $(cc) -o $(prom) $(obj) %.o: %.cpp $(deps) $(cc) $(CXXFLAGS) $(FLAG) -c $< -o $@ .PHONY: clean clean: rm -rf $(prom) $(obj) ``` ## 3.2 一些备注 ### 3.2.1 宏(macro) `shell`中有和`C++`中的`#define`类似的宏替换。如上段代码中开头几行声明的,它们并不是“变量”,而是宏定义,这意味着机器会把它们的定义直接粘贴替换`${var_name}`。因此在`shell`中不妨试一试: ```shell m = 1 + 1 echo $[$m * 4] ``` 你会发现`$m * 4`是`1 + 1 * 4`,因此输出是`5`,而不是`8`。 **但是**,如果你`echo $[m*4]`,输出就是`8`。我还不清楚这是什么原因,欢迎留言。 用宏定义方便了编译器、编译参数的一次性修改。配合通配符方便了获取需要的文件。 ### 3.2.2 .PHONY 让我们看看 ~~Colin~~ Collins 对phony的解释: > ADJ If you describe something as **phony**, you disapprove of it because it is false rather than genuine. 假的 [非正式] 字面意思,`.PHONY: `将其后面的目标指定为“伪目标”。至于其作用,我们首先需要明确以下几点。这几点是初学者最迷惑的几点,也是Makefile的核心内容,即“目标”(或者“伪目标”)、“依赖”,以及目标下一行的那段代码它们之间的关系。 1. 正如模板中的`$(prom)`(宏)、`%.o`(通配符)所代表的目标文件如`main` 、 `main.o`,`clean`也是“目标”。 2. **`make`会且只会完成一个终极目标。这一终极目标是从前往后的第一个。**而为了完成这一个终极目标(如`main`),`make`会依次检查终极目标后面列出的依赖项(如`main.o`),如果依赖不存在则会一层一层向下找依赖的生成方法(`main.o`目标)并执行,最终完成终极目标。 3. 对于简单的写法,如果我们想实现clean功能,我们直接在`Makefile`最后添加以下代码即可。 ```makefile clean: rm -rf $(prom) $(obj) ``` 4. 根据上述,目标`clean` 既不是第一个目标(它被写在最后),也不是终极目标的依赖项。因此仅仅执行`make`,`clean`直接就被忽略了。所以需要执行`make clean`,指定`make`的目标为`clean`才行。 5. 之所以添加后执行`make clean`第二行的命令会被执行,是因为当前文件夹下不存在名字为`clean`的文件,因此`make`“想通过执行这行命令来生成`clean`文件”(这是为了方便理解臆想出来的目的)。而因为这行命令本身又没有创建`clean`文件,因此每次`clean`都不存在,因而每次第二行命令都会被执行。因此我们是**利用了`make`以为`clean`是个需要生成的文件这一特性来“变相”达到了目的。然而其实`clean`并不是文件。** 然后我们再来解释`.PHONY`的作用。 `clean`后面的列表是空的,也就是没有依赖文件。回顾 ```makefile main.o: main.cpp g++ -c main.cpp -o main.o ``` 中,如果依赖`main.cpp`在现有的`main.o`创建之后没有发生过变化,那么第二行的命令将不会被执行。考虑特殊情况:文件夹下正好有个文件名为`clean`,而`Makefile`中,目标`clean`没有依赖(因此不存在依赖被更新后目标需要被更新的情况),而它又存在(因为前面说了正好有个文件名为`clean`)(所以就是“最终版本”),那么`make`显然就不会“试图执行第二行命令去生成它”。于是我们的`make clean`就失效了。(这段话可能比较难理解,因为`clean`时而是`Makefile`中的目标,时而是文件。) 而为了解决这一问题,`.PHONY: `将其后面罗列的目标指定为“伪目标”。所谓伪目标,就是告诉`make`,`clean`就不是一个文件目标,而仅仅是个“假的目标”,它是它下面那段需要被执行的代码的一个“label”,无论如何它后面的命令都需要被执行。于是,即使文件夹下正好有个文件名为`clean`,当你执行`make clean`时,由于`make`已经知道了目标`clean`的目的不是去生成一个名叫`clean`的文件,而是要去执行它所代表的下面那行命令,因此`make`就会无条件地执行第二行的命令。 ### 3.2.3 all 上面提到,`Makefile`中有且仅有一个终极目标,而如果我们想在`make`中一次性完成多个“终极目标”怎么办呢?这就需要`all`。 ```makefile all: main1 main2 main1: main1.cpp g++ main1.cpp -o main1 main2: main2.cpp g++ main2.cpp -o main2 ``` 如上,`all`放在第一个作为“终极目标”,其依赖为`main1`和`main2`,这样在完成依赖项的构建时,`main1`和`main2`就都会产生。在这里,`all`也不是一个“文件目标”,但是它也不是如`clean`一样的“伪目标”。首先,我们不能给它前面加上`.PHONY`,否则整个文件后面所有的目标都成了伪目标,但是`main1`这样的目标可是“文件目标”。其次,它有依赖项,但是没有第二行命令。当第一次生成了`main1`和`main2`之后,如果你修改了相关源代码,但是没有删除`main1`和`main2`就执行`make`,那显然什么都不会发生。因此如果改了生成`main1`和`main2`的源文件,你需要先删除`main1`和`main2`,再`make`才可以得到新的`main1`和`main2`。 ------ 其它参考资料: [Makefile编译选项CC与CXX/CPPFLAGS、CFLAGS与CXXFLAGS/LDFLAGS](https://www.cnblogs.com/lidabo/p/6068448.html) [makefile中的.PHONY和all的作用](https://blog.csdn.net/tonglin12138/article/details/88636170) [Makefile中.PHONY的作用](https://www.cnblogs.com/idorax/p/9306528.html) [1]: https://blog.valderfield.com/tag/OOP/ [2]: https://blog.valderfield.com/usr/uploads/2020/02/2166209756.png Last modification:May 12, 2021 © Allow specification reprint Support Appreciate the author Like 0 如果觉得我的文章对你有用,请随意赞赏