[关闭]
@zhengyuhong 2016-03-23T10:10:04.000000Z 字数 10819 阅读 1485

跟我一起写Makefile读书笔记

读书笔记 linux makefile C++ C


3、Makefile介绍

  make 命令执行时,需要一个 Makefile 文件,以告诉 make 命令需要怎么样的去编译和链接程序。

3.1、Makefile的规则

  1. target : prerequisites
  2. command

  target 也就是一个目标文件,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
  prerequisites 就是,要生成那个 target 所需要的文件或是目标。
  command 也就是 make 需要执行的命令。(任意的 Shell 命令,且必须以tab键开头)
  prerequisites中如果有一个以上的文件比target 文件要新的话,command 所定义的命令就会被执行。这就是 Makefile 的规则。也就是 Makefile 中最核心的内容。

3.2、示例

  正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的 Makefile 应该是下面的这个样子的:

  1. #cat Makefile
  2. edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  3. cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  4. main.o : main.c defs.h
  5. cc -c main.c
  6. kbd.o : kbd.c defs.h command.h
  7. cc -c kbd.c
  8. command.o : command.c defs.h command.h
  9. cc -c command.c
  10. display.o : display.c defs.h buffer.h
  11. cc -c display.c
  12. insert.o : insert.c defs.h buffer.h
  13. cc -c insert.c
  14. search.o : search.c defs.h buffer.h
  15. cc -c search.c
  16. files.o : files.c defs.h buffer.h command.h
  17. cc -c files.c
  18. utils.o : utils.c defs.h
  19. cc -c utils.c
  20. clean :
  21. rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

  在这个 Makefile 中,目标文件(target)包含:执行文件 edit 和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h 文件。每一个 .o 文件都有一组依赖文件,而这些 .o文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
  在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个 Tab 键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make 会比较 targets 文件和 prerequisites 文件的修改日期,如果prerequisites 文件的日期要比 targets文件的日期要新,或者 target 不存在的话,那么,make就会执行后续定义的命令。
  这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像 C 语言中的 lable 一样,其冒号后什么也没有,那么,make 就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在 make 命令后明显得指出这个lable 的名字。这样的方法非常有用,我们可以在一个 makefile 中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

3.3、make是如何工作

在默认的方式下,也就是我们只输入 make 命令。那么,
  1、make 会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
  3、如果 edit 文件不存在,或是 edit 所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成 edit 这个文件。
  4、如果 edit 所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到再根据上一个规则生成.o文件。(一个递归过程)
  5、 当然,你的 C 文件和 H 文件是存在的啦, 于是 make 会生成 .o 文件, 然后再用 .o 文件生命 make 的终极任务,也就是执行文件 edit 了。这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
  通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要 make 执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
  于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标 file.o 会被重编译(也就是在这个依性关系后面所定义的命令),于是 file.o 的文件也是最新的啦,于是 file.o 的文件修改时间要比edit 要新,所以 edit 也会被重新链接了(详见 edit 目标文件后定义的命令)。
  而如果我们改变了“command.h”,那么,kdb.o、command.o 和 files.o 都会被重编译,并且,edit 会被重链接。

3.4、Makefile中使用变量

  我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在 clean 中)。当然,我们的 makefile并不复杂,所以在两个地方加也不累,但如果 makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了 makefile 的易维护,在 makefile 中我们可以使用变量。makefile 的变量也就是一个字符串,理解成 C 语言中的宏可能会更好。

  1. objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o

改良版 makefile 就变成下面这个样子:

  1. objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  2. edit : $(objects)
  3. cc -o edit $(objects)
  4. clean :
  5. rm edit $(objects)

  于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

3.5、让make自动推导

  GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导命令。
  只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make 找到一个 whatever.o,那么 whatever.c,就会是 whatever.o 的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的 makefile 再也不用写得这么复杂。我们的是新的 makefile 又出炉了。

  1. objects = main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. edit : $(objects)
  4. cc -o edit $(objects)
  5. main.o : defs.h
  6. kbd.o : defs.h command.h
  7. command.o : defs.h command.h
  8. display.o : defs.h buffer.h
  9. insert.o : defs.h buffer.h
  10. search.o : defs.h buffer.h
  11. files.o : defs.h buffer.h command.h
  12. utils.o : defs.h
  13. .PHONY : clean
  14. clean :
  15. rm edit $(objects)

3.6、另类风格的 makefile

  即然我们的 make 可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于 make 来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的 makefile 吧。

  1. objects = main.o kbd.o command.o display.o \
  2. insert.o search.o files.o utils.o
  3. edit : $(objects)
  4. cc -o edit $(objects)
  5. $(objects) : defs.h
  6. kbd.o command.o files.o : command.h
  7. display.o insert.o search.o files.o : buffer.h
  8. .PHONY : clean
  9. clean :
  10. rm edit $(objects)

  这种风格,让我们的 makefile 变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o 文件,那就理不清楚了。

3.7、清空目标文件的规则 

  每个 Makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

  1. clean:
  2. rm edit $(objects)
  3. #更为稳健的做法是:
  4. .PHONY : clean
  5. clean :
  6. -rm edit $(objects)

前面说过,.PHONY 意思表示 clean 是一个“伪目标”,。而在 rm 命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean 的规则不要放在文件的开头,不然,这就会变成 make 的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean 从来都是放在文件的最后”

4、Makefile总述

4.1、makefile里有什么?

4.1.1、显式规则。
  显式规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
4.1.2、隐晦规则。
  由于我们的 make 有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile,这是由 make 所支持的。
4.1.3、变量的定义。
  在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点你 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
4.1.4、文件指示。
  其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像 C 语言中的include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像 C 语言中的预编译#if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
4.1.5、注释。
  Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++中的“//”一样。如果你要在你的 Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。
最后,还值得一提的是,在 Makefile 中的命令,必须要以[Tab]键开始。

4.3、引用其他的Makefile

  在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include 的语法是:include

4.5、make的工作方式

GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是类似)
1、读入所有的 Makefile。
2、读入被 include 的其它 Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1-5 步为第一个阶段,6-7 为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make 会把其展开在使用的位置。但 make 并不会完全马上展开,make 使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

5、书写规则

  规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
在 Makefile 中,规则的顺序是很重要的,因为,Makefile 中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让 make 知道你的最终目标是什么。一般来说,定义在 Makefile 中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。 make所完成的也就是这个目标。

5.2、规则的语法

  1. targets : prerequisites
  2. command
  3. #或是这样:
  4. targets : prerequisites ; command

  command 是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头, 如果和 prerequisites 在一行,那么可以用分号做为分隔。(见上) prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。

5.3、在规则中使用通配符

  如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make 支持三各通配符: “*”,“?”和“[...]”。这是和 Unix 的 B-Shell 是相同的。 波浪号 (“~”)字符在文件名中表示用户主目录。

5.5、伪目标

  因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
  当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向 make 说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:

  1. .PHONY: clean
  2. clean:
  3. rm *.o temp

  伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的 Makefile 需要一口气生成若干个可执行文件,但你只想简单地敲一个 make 完事,并且,所有的目标文件都写在一个 Makefile 中,那么你可以使用“伪目标”这个特性:

  1. all : prog1 prog2 prog3
  2. .PHONY : all
  3. prog1 : prog1.o utils.o
  4. cc -o prog1 prog1.o utils.o
  5. prog2 : prog2.o
  6. cc -o prog2 prog2.o
  7. prog3 : prog3.o sort.o utils.o
  8. cc -o prog3 prog3.o sort.o utils.o

  我们知道,Makefile 中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。比如:

  1. CC = gcc
  2. objects = foo.o bar.o
  3. all: $(objects)
  4. $(objects): %.o: %.c#将以".o"结尾全部替换为".c"结尾
  5. $(CC) $(CFLAGS) -c $< -o $@

  上面的例子中,指明了我们的目标从$objects 中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$objects 集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”), “$@”表示目标集 (也就是“foo.o bar.o”)。
  Makefile中通配符*与%,*表示任意字符,%表示匹配零或若干字符,是否可以通用?
  此两者均为通配符,但更准确的讲,%为Makefile规则通配符,一般用于规则描述,如

  1. %.o : %.c
  2. $(CC) $< -o $@

表示所有的目标文件及其依赖文件

5.8、自动生成依赖性

  在 Makefile 中, 我们的依赖关系可能会需要包含一系列的头文件,比如, 如果我们的 main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:

  1. main.o : main.c defs.h

  但是,如果是一个比较大型的工程,你必需清楚哪些 C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改 Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用 C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:

  1. cc -M main.c
  2. 其输出是:
  3. main.o : main.c defs.h

  于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用 GNU 的 C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。更多本章节内容,请自行查阅原文。

6、书写命令 

6.1、显示命令

  通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被 make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

  1. @echo 正在编译 XXX 模块......

当 make 执行时,会输出“正在编译 XXX 模块......”字串,但不会输出命令,如果没有“@”,那么,make 将输出:

  1. echo 正在编译 XXX 模块......
  2. 正在编译 XXX 模块......

7、使用变量

  在 Makefile 中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在 Makefile 中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在 Makefile 中改变其值。在Makefile中,变量可以使用在“目标”,“依赖目标”,“命令”或是 Makefile 的其它部分中。变量的命名字可以包含字符、数字,下划线,但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的, 传统的 Makefile变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。
  为了避免变量递归赋值,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是“:=”操作符为了避免上面的这种方法,我们可以使用 make 中的另一种用变量来定义变量的方法。这种方法使用的是“:=”操作符
  还有一个比较有用的操作符是“?=”,先看示例:

  1. FOO ?= bar

  其含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做。与shell中的变量"?="一致

7.4、追加变量

  1. 我们可以使用“+=”操作符给变量追加值,如:
  2. objects = main.o foo.o bar.o utils.o
  3. objects += another.o

7.8、目标变量

  前面我们所讲的在 Makefile 中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。当然,“自动化变量”除外, 如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。当然,我样同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

  1. prog : CFLAGS = -g
  2. prog : prog.o foo.o bar.o
  3. $(CC) $(CFLAGS) prog.o foo.o bar.o

  在这个示例中,不管全局的$(CFLAGS)的值是什么,在 prog 目标,以及其所引发的所有规则中(prog.o foo.o bar.o 的规则),$(CFLAGS)的值都是“-g”

7.9、模式变量

  在 GNU 的 make 中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
  我们知道,make 的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:

  1. %.o : CFLAGS = -O

11、隐含规则

11.3、隐含规则使用的变量

AR
函数库打包程序。默认命令是“ar”。
AS
汇编语言编译程序。默认命令是“as”。
CC
C 语言编译程序。默认命令是“cc”。
CXX
C++语言编译程序。默认命令是“g++”。
CPP
C 程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
RM
删除文件命令。默认命令是“rm –f”。

ARFLAGS
函数库打包程序 AR 命令的参数。默认值是“rv”。
ASFLAGS
汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
C 语言编译器参数。
CXXFLAGS
C++语言编译器参数。
CPPFLAGS
C 预处理器参数。( C 和 Fortran 编译器也会用到)。
LDFLAGS
链接器参数。(如:“ld”)

11.5、定义模式规则

  你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有"%"字符。"%"的意思是表示一个或多个任意字符。在依赖目标中同样可以使用"%",只是依赖目标中的"%"的取值,取决于其目标。有一点需要注意的是,"%"的展开发生在变量和函数的展开之后,变量和函数的展开发生在 make 载入 Makefile 时,而模式规则中的"%"则发生在运行时。
  模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的"%"定义表示对文件名的匹配,"%"表示长度任意的非空字符串。例如:"%.c"表示以".c"结尾的文件名 (文件名的长度至少为 3),而"s.%.c"则表示以"s."开头, ".c"结尾的文件名 (文件名的长度至少为 5)
  如果"%"定义在目标中,那么,目标中的"%"的值决定了依赖目标中的"%"的值,也就是说,目标中的模式的"%"决定了依赖目标中"%"的样子。例如有一个模式规则如下:

  1. %.o : %.c
  2. <command ......>

其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标是"a.o b.o",那么"%c"就是"a.c b.c"。
  下面这个例子表示了,把所有的[.c]文件都编译成[.o]文件.

  1. %.o : %.c
  2. $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

  自动化变量及其说明
$@
  表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$<
  依赖目标中的第一个目标名字。如果依赖目标是以模式( 即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
  所有比目标新的依赖目标的集合。以空格分隔。
$^
  所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$%
  仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows 下是[.lib]),那么,其值为空。

12、使用make更新函数库文件

  函数库文件也就是对 Object文件(程序编译的中间文件)的打包文件。在 Unix 下,一般是由命令"ar"来完成打包工作。
  一个函数库文件由多个文件组成。你可以以如下格式指定函数库文件及其组成:

  1. archive(member)

  这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了"ar"命令来服务的。如:

  1. foolib(hack.o) : hack.o
  2. ar cr foolib hack.o

如果要指定多个 member,那就以空格分开,如:

  1. foolib(hack.o kludge.o)

你还可以使用 Shell 的文件通配符来定义,如:

  1. foolib(*.o)

11.2 函数库成员的隐含规则

  当 make 搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是"a(m)"形式的,其会把目标变成"(m)"。于是,如果我们的成员是"%.o"的模式定义,并且如果我们使用"make foo.a(bar.o)"的形式调用 Makefile 时,隐含规则会去找"bar.o"的规则,如果没有定义 bar.o 的规则,那么内建隐含规则生效,make 会去找 bar.c 文件来生成 bar.o,如果找得到的话,make 执行的命令大致如下:

  1. cc -c bar.c -o bar.o
  2. ar r foo.a bar.o
  3. rm -f bar.o
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注