文章目录1. 编译器gcc和g1.1 gcc vs g有什么区别1.2 gcc编译选项1.2.1 预处理进行宏替换1.2.2 编译生成汇编1.2.3 汇编生成机器可识别代码1.2.4 链接生成可执行文件或者库文件思考一些问题2. 动态链接和静态链接2.1 静态连接2.2 动态连接3. 动态库和静态库3.1 静态库 .a结尾libxxx.a)3.2 动态库 .so结尾(libxxx.so)4. 自动化构建-make/Makefile4.1 写一个最简单的make/makefile4.2 介绍依赖关系和依赖方法4.3 解析 .PHONY4.3.1 .PHONY的本质4.3.2 为什么需要.PHONY4.3.3 为什么普通编译目标不用.PHONY4.4.4 .PHONY的底层原理4.5 总结推导过程4.6 扩展makefile的语法1. 编译器gcc和g1.1 gcc vs g有什么区别gccC 语言编译器默认按 C 语言标准编译链接时不自动链接 C 标准库gC 语言编译器默认按 C 语言标准编译会自动链接 C 标准库libstdc并处理 C 特有的名称修饰name mangling建议还是gcc编C语言g编C代码各司其职1.2 gcc编译选项gcc和g二者的选项完全一致gcc[选项]要编译的文件[选项]目标文件接下来我们用另一种视角重新理解翻译程序的过程1.2.1 预处理进行宏替换预处理的基本功能头文件展开编译器会把头文件拷贝到源文件内部–头文件展开头文件拷到源文件里头文件声明和定义放一起为了制作成库必须把头文件和源文件分开达到一个保护的效果。去注释去除注释宏替换替换条件编译条件编译的本质是在对代码做裁剪编译器能去注释也就说明编译器具有裁剪的功能。那为什么编译器会有裁剪这个功能有利于代码维护特定条件下的特定功能。类似于一款软件有免费的vs收费的公司内部是如何维护这种项目代码的他们仅仅需要维护收费的收费的接口留下来剩下的就是把其余接口隐藏起来这就可以利用到条件编译了。预处理是以#开头的代码行实例gcc-Ehello.c-ohello.i选项-E的作用是让gcc在预处理结束后停止编译的过程选项-o说明后面的文件是目标文件“.i”为已经处理过的C原始程序1.2.2 编译生成汇编在这个阶段中gcc首先要检查代码的规范性、是否有语法错误等、以确定代码实际要做的工作检查无误后将代码翻译成汇编语言将高级语言翻译成汇编语言-S程序开始翻译将高级语言翻译成汇编语言就停下来实例gcc-Stext.i-otext.s1.2.3 汇编生成机器可识别代码将汇编语言翻译为二进制文件.o:可重定位目标二进制文件汇编阶段是把编译阶段生成的“.s”文件转成目标文件使用选项-c就可以看到汇编代码已经转化为“.o”的二进制目标文件示例gcc-ctext.s-otext.o.o可重定位二进制文件不能直接运行因为这里没有声明没有定义没有链接gcc只有库和头文件在哪里需要连接后才能运行。但实际更常用的写法是直接从 .c生成 .o这里只做演示。1.2.4 链接生成可执行文件或者库文件编译成功之后就可以链接了gcc text.o-otext总结选项作用输出文件-E仅预处理宏展开、头文件展开、去注释.i-S预处理 编译生成汇编代码.s-c预处理 编译 汇编生成目标文件.o无完整编译链接生成可执行文件默认为a.out思考一些问题为什么要把C语言翻译成汇编语言历史是由汇编发展过来的你的计算机为什么叫二进制计算机因为二进制可以被CPU所执行被CPU运算。指令集会被设计成二进制把指令合起来这个就是程序。然而翻译语言的本质也就是将高级语言转化成CPU能识别的指令集。古早时候程序员都是用二进制编程的纸带编程后面就引入了汇编语言汇编语言就是将所有指令用单词进行映射形成一种更好理解的语言。一种语言的的发明就意味着对应的编译器被发明出来这个时候编译器就诞生了。编译器的核心工作就是将汇编语言翻译为机器语言随着语言的不断演进每发明一门语言就需要有配套的编译器保证其能被翻译为机器码所以我们说语法的本质是编译器的翻译规则。这个时候就又引进了一个问题3. 我们是先有的编译器还是先有对应的语言我们说语言的本质是编译器的规则规则不明确是没有语言的那应该是先有语言再有编译器的。我们知道汇编语言的编译器是由汇编语言写的我们不知道翻译的规则怎么写出的汇编语言我们知道了翻译规则没有汇编语言怎么写编译这就陷入了鸡生蛋蛋生鸡的问题。所以说最早的汇编语言编译器是用机器码手工写二进制/十六进制编写的二进制语言写完编译器逐步发展出汇编语言汇编语言不断发展发展到后面汇编语言再用它自己逐步的重写编译器这个过程就叫做–编译自举。4. 开发一门语言最必要的条件有那些编译器、头文件、库文件这三个条件缺一不可平时我们安装开发包的过程就是安装头文件与库2. 动态链接和静态链接2.1 静态连接在我们的实际开发中不可能将所有代码放在一个源文件中所以会出现多个源文件而且多个源文件之间不是独立的而会存在多种依赖关系如一个源文件可能要调用另一个源文件中定义的函数但是每个源文件都是独立编译的即每个.c文件会形成一个.o文件为了满足前面说的依赖关系则需要将这些源文件产生的目标文件进行链接从而形成一个可以执行的程序。这个链接的过程就是静态链接。静态链接的缺点很明显静态链接实际上就是将我们所需要的库中的方法直接拷贝到我的程序中其优点是不需要库跳转一旦链接成功可执行程序不再需要静态库再次链接。但是其缺点也很明显浪费空间在静态链接下每个可执行程序都会将所依赖的库代码如 printf.o完整地拷贝并嵌入到自身的二进制文件中。如果多个程序依赖同一个库会导致磁盘空间的严重冗余。同时虽然现代操作系统通过“写时复制”机制在物理内存中共享了只读的代码段但每个进程仍需在虚拟内存中独立映射这些代码增加了系统的整体资源开销。更新比较困难因为每当库函数的代码修改了这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是在可执行程序中已经具备了所有执行程序所需要的任何东西在执行的时候运行速度快。2.2 动态连接动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。[ldd]命令可以用于打印程序或者库文件所依赖的共享列表可以看到这里用到了一个C语言动态链接库。所以,我们应该如何理解库我们可以举一个简单的例子我们的C程序中并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用 。库的本质其实就是方法的集合。3. 动态库和静态库3.1 静态库 .a结尾libxxx.a)静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了其后缀名一般为“.a”。3.2 动态库 .so结尾(libxxx.so)动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时再链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。和动态库产生关联的过程叫做动态链接一旦库缺失所有相关程序都运行不了。动态链接的是关联关系是通过地址完成的。编译程序我们通常用动态库和动态链接是最佳实践我们可以强制使用静态链接编译看看。安装静态库默认是没有安装的-static 链接成静态的静态链接大小远大于动态链接会导致内存占用过大。gcc默认生成的二进制程序是动态链接的这点可以通过[file] 命令验证。4. 自动化构建-make/Makefile上面描述的编译链接是针对某一个文件的那么如果我们有后面的工程项目中我们引入头文件、还各种源文件需要一起编译链接如果有100个那么我们是不是要一个命令一个命令的编译100次呢那是非常低效的一种方式。那么有没有什么方法能批量化的构建我们的项目呢那么我们就引入了自动化构建方式–make。构建编译链接运行makefile带来的好处就是—“自动化编译”一旦写好只需要一个make命令整个工程完全自动编译极大的提高了软件开发的效率。4.1 写一个最简单的make/makefilemake是一条命令makefile是一个文件make是一个命令工具是一个解释makefile 中指令的命令工具一般来说大多数的IDE都有这个命令比如Delphi的makeVisual C的nmakeLinux下GNU的make。可见makefile都成为了一种在工程方面的编译方法。新建一个文件Makefilemakefile也是可以的准备一个text.c打印一个HelloWordvim打开Makefile写入[make]运行该Makefile4.2 介绍依赖关系和依赖方法依赖关系和依赖方法结合必须成对且具有合理性我们人与人之间的来往也是构建在依赖关系和依赖方法上的。结合实际的运用我们可以加上一个清理的过程text.exe:text.c gcc text.c-otext.exe .PHONY:clean clean:rm-ftext.exe text.i text.s text.o我们可以总结出四条结论依赖关系必须存在但是依赖文件列表可以为空依赖方法可以是任何shell命令clean目标只是利用make的自动推导能力让他执行rm命令在构建工程的视角看起来就是清理项目但是其本质是清理不需要的临时文件。make后面可以跟目标名字后面跟着谁就解析谁的依赖关系和依赖方法make默认只会推导一条完整的推导链路并且是推导第一个依赖关系对应的推导。4.3 解析 .PHONY工程是需要被清理的:像clean这种没有被第一个目标文件直接或间接关联那么它后面所定义的命令将不会被自动执行。不过我们可以显示要make执行。即命令—“make clean”以此来清除所有的目标文件以便重编译。但是一般我们这种clean的目标文件我们将它设置为伪目标,用.PHONY 修饰,伪目标的特性是总是被执行的。什么叫做总是被执行文件内容属性Modify内容变更时间更新Change属性变更时间更新Access文件最近被访问的时间什么叫访问catstat都叫访问文件一个文件内容被修改是要从内存刷新到磁盘的更新的内容都要刷新到磁盘上查看要比修改的次数多。输出结论.PHONY让目标总是执行的根本原因在于让make忽略源文件和可执行目标文件的时间对比补充touch可以修改文件所对应的事件4.3.1 .PHONY的本质.PHONY 是 Makefile 中的一个特殊目标声明它的作用是告诉 make这个目标不是真实文件不要检查它是否存在也不要比较它的时间戳每次都直接执行它所对应的命令。4.3.2 为什么需要.PHONYmake 的默认逻辑是根据“目标和依赖文件的修改时间”来决定是否执行命令。如果目标文件已存在且比所有依赖文件都新就跳过执行。对于 clean 这类目标它的目的不是生成一个叫 clean的文件而是执行删除操作。如果没有 .PHONY 声明一旦当前目录下恰好有一个名为 clean的文件make 就会认为这个目标“已经满足条件”直接跳过导致清理命令根本不执行。4.3.3 为什么普通编译目标不用.PHONY像 text.exe这种目标需要生成真实文件它的执行必须依赖“文件时间戳”来判断是否需要更新——这样才支持增量编译即只重新编译改过的文件没改过的不动从而提高构建效率。如果给 text.exe加上 .PHONY每次都会强制重链反而浪费性能。4.4.4 .PHONY的底层原理make 在解析 Makefile 时会把 .PHONY 声明的目标标记为“非文件目标”。在执行阶段make 会直接跳过文件存在性检查和时间戳比较无条件执行命令。因此这种目标总是被执行——它是被规则驱动而不是被文件驱动。4.5 总结推导过程下面我们逐步拆解上面text.c-text.exe的过程。text.exe: text.o gcc-otext.exe text.o text.o: text.s gcc-ctext.s-otext.o text.s: text.i gcc-Stext.i-otext.s text.i: text.c gcc-Etext.c-otext.imake执行make命令会逐一解析makefile,会形成推导栈推导栈其实就是依赖关系的集合。总结make是如何工作的make会在当前目录下找名字叫“Makefile”或“makefile”的文件。如果找到它会找文件中的第一个目标文件target在上面的例子中他会找到text.exe这个文件并把这个文件作为最终的目标文件。如果该文件不存在或者说是text.exe所依赖的后面的text.o文件的文件修改时间要比text.exe这个文件新可以用touch测试那么他就会执行后面所定义的命令来生成text.exe这个文件。如果text.exe所依赖的text .o文件不存在那么make 会在当前文件中找目标为text .o 文件的依赖性如果找到则再根据那一个规则生成text.o 文件。这有点像一个堆栈的过程你的C文件和H文件是存在的于是make 会生成text .o 文件然后再用text.o 文件声明make 的终极任务也就是执行文件hello 了这就是整个make 的依赖性make会一层又一层地去找文件的依赖关系直到最终编译出第一个目标文件。在找寻的过程中如果出现错误比如最后被依赖的文件找不到那么make就会直接退出并报错而对于所定义的命令的错误或是编译不成功make是不会理的。make只管文件的依赖性即如果在我找了依赖关系之后冒号后面的文件还是不在那么make就不工作了。4.6 扩展makefile的语法make会将命令回显回来怎样不回显命令?每个命令前面写形成可执行还要清理或重新生成。2. 如果我有一天想要修改文件名那么通篇都需要改这样很不友好。那么我们可以BINtext.exe SRCtext.c $(BIN):$(SRC) echo 开始编译代码 gcc -o $ $^ echo 编译完成 .PHONY:clean clean: echo 清理工程。。。 rm -f $(BIN) echo 清理完毕。。。从此往后改就只需要改最上面就可以了。3. 要是我们的项目中出现了100个源文件我们应该如何编译首先建立100个源文件touchsrc{1..100}.c同时这批文件还可以批量删除rmsrc{1..100}.c我们不可能将上述文件一个一个编译修改makefile小细节下述二者都能生成.o文件gcc -c src1.c会自动生成 src1.o省略了 -o参数更简洁。gcc-csrc1.c-osrc1.o gcc-csrc1.cMakefile的最佳实践可以解决绝大部分的自动化构建问题BINtext.exe #SRC$(shell ls *.c)#方法一 SRC$(wildcard *.c)#wildcard函数获取当前目录下的所有原文件 OBJ$(SRC:.c.o)#将.c换成.o CCgcc #定义变量或者指令便于后期替换 Echoecho Rmrm -f $(BIN):$(OBJ) $(CC) -o $ $^ #交代一下各个.o文件应该怎么编.o文件当前并不存在 $(Echo) linking $^ to $...done %.o:%.c $(CC) -c $ #表示第一个依赖文件 $(Echo) compling $ to $...done .PHONY:clean clean: $(Rm) $(OBJ) $(BIN) .PHONY:text text: $(Echo) Debug--- $(Echo) $(SRC); $(Echo) Debug--- $(Echo) $(OBJ); $(Echo) Debug---欢迎大家批评指正
Linux开发工具:代码的编译器与自动化构建过程
文章目录1. 编译器gcc和g1.1 gcc vs g有什么区别1.2 gcc编译选项1.2.1 预处理进行宏替换1.2.2 编译生成汇编1.2.3 汇编生成机器可识别代码1.2.4 链接生成可执行文件或者库文件思考一些问题2. 动态链接和静态链接2.1 静态连接2.2 动态连接3. 动态库和静态库3.1 静态库 .a结尾libxxx.a)3.2 动态库 .so结尾(libxxx.so)4. 自动化构建-make/Makefile4.1 写一个最简单的make/makefile4.2 介绍依赖关系和依赖方法4.3 解析 .PHONY4.3.1 .PHONY的本质4.3.2 为什么需要.PHONY4.3.3 为什么普通编译目标不用.PHONY4.4.4 .PHONY的底层原理4.5 总结推导过程4.6 扩展makefile的语法1. 编译器gcc和g1.1 gcc vs g有什么区别gccC 语言编译器默认按 C 语言标准编译链接时不自动链接 C 标准库gC 语言编译器默认按 C 语言标准编译会自动链接 C 标准库libstdc并处理 C 特有的名称修饰name mangling建议还是gcc编C语言g编C代码各司其职1.2 gcc编译选项gcc和g二者的选项完全一致gcc[选项]要编译的文件[选项]目标文件接下来我们用另一种视角重新理解翻译程序的过程1.2.1 预处理进行宏替换预处理的基本功能头文件展开编译器会把头文件拷贝到源文件内部–头文件展开头文件拷到源文件里头文件声明和定义放一起为了制作成库必须把头文件和源文件分开达到一个保护的效果。去注释去除注释宏替换替换条件编译条件编译的本质是在对代码做裁剪编译器能去注释也就说明编译器具有裁剪的功能。那为什么编译器会有裁剪这个功能有利于代码维护特定条件下的特定功能。类似于一款软件有免费的vs收费的公司内部是如何维护这种项目代码的他们仅仅需要维护收费的收费的接口留下来剩下的就是把其余接口隐藏起来这就可以利用到条件编译了。预处理是以#开头的代码行实例gcc-Ehello.c-ohello.i选项-E的作用是让gcc在预处理结束后停止编译的过程选项-o说明后面的文件是目标文件“.i”为已经处理过的C原始程序1.2.2 编译生成汇编在这个阶段中gcc首先要检查代码的规范性、是否有语法错误等、以确定代码实际要做的工作检查无误后将代码翻译成汇编语言将高级语言翻译成汇编语言-S程序开始翻译将高级语言翻译成汇编语言就停下来实例gcc-Stext.i-otext.s1.2.3 汇编生成机器可识别代码将汇编语言翻译为二进制文件.o:可重定位目标二进制文件汇编阶段是把编译阶段生成的“.s”文件转成目标文件使用选项-c就可以看到汇编代码已经转化为“.o”的二进制目标文件示例gcc-ctext.s-otext.o.o可重定位二进制文件不能直接运行因为这里没有声明没有定义没有链接gcc只有库和头文件在哪里需要连接后才能运行。但实际更常用的写法是直接从 .c生成 .o这里只做演示。1.2.4 链接生成可执行文件或者库文件编译成功之后就可以链接了gcc text.o-otext总结选项作用输出文件-E仅预处理宏展开、头文件展开、去注释.i-S预处理 编译生成汇编代码.s-c预处理 编译 汇编生成目标文件.o无完整编译链接生成可执行文件默认为a.out思考一些问题为什么要把C语言翻译成汇编语言历史是由汇编发展过来的你的计算机为什么叫二进制计算机因为二进制可以被CPU所执行被CPU运算。指令集会被设计成二进制把指令合起来这个就是程序。然而翻译语言的本质也就是将高级语言转化成CPU能识别的指令集。古早时候程序员都是用二进制编程的纸带编程后面就引入了汇编语言汇编语言就是将所有指令用单词进行映射形成一种更好理解的语言。一种语言的的发明就意味着对应的编译器被发明出来这个时候编译器就诞生了。编译器的核心工作就是将汇编语言翻译为机器语言随着语言的不断演进每发明一门语言就需要有配套的编译器保证其能被翻译为机器码所以我们说语法的本质是编译器的翻译规则。这个时候就又引进了一个问题3. 我们是先有的编译器还是先有对应的语言我们说语言的本质是编译器的规则规则不明确是没有语言的那应该是先有语言再有编译器的。我们知道汇编语言的编译器是由汇编语言写的我们不知道翻译的规则怎么写出的汇编语言我们知道了翻译规则没有汇编语言怎么写编译这就陷入了鸡生蛋蛋生鸡的问题。所以说最早的汇编语言编译器是用机器码手工写二进制/十六进制编写的二进制语言写完编译器逐步发展出汇编语言汇编语言不断发展发展到后面汇编语言再用它自己逐步的重写编译器这个过程就叫做–编译自举。4. 开发一门语言最必要的条件有那些编译器、头文件、库文件这三个条件缺一不可平时我们安装开发包的过程就是安装头文件与库2. 动态链接和静态链接2.1 静态连接在我们的实际开发中不可能将所有代码放在一个源文件中所以会出现多个源文件而且多个源文件之间不是独立的而会存在多种依赖关系如一个源文件可能要调用另一个源文件中定义的函数但是每个源文件都是独立编译的即每个.c文件会形成一个.o文件为了满足前面说的依赖关系则需要将这些源文件产生的目标文件进行链接从而形成一个可以执行的程序。这个链接的过程就是静态链接。静态链接的缺点很明显静态链接实际上就是将我们所需要的库中的方法直接拷贝到我的程序中其优点是不需要库跳转一旦链接成功可执行程序不再需要静态库再次链接。但是其缺点也很明显浪费空间在静态链接下每个可执行程序都会将所依赖的库代码如 printf.o完整地拷贝并嵌入到自身的二进制文件中。如果多个程序依赖同一个库会导致磁盘空间的严重冗余。同时虽然现代操作系统通过“写时复制”机制在物理内存中共享了只读的代码段但每个进程仍需在虚拟内存中独立映射这些代码增加了系统的整体资源开销。更新比较困难因为每当库函数的代码修改了这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是在可执行程序中已经具备了所有执行程序所需要的任何东西在执行的时候运行速度快。2.2 动态连接动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。[ldd]命令可以用于打印程序或者库文件所依赖的共享列表可以看到这里用到了一个C语言动态链接库。所以,我们应该如何理解库我们可以举一个简单的例子我们的C程序中并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用 。库的本质其实就是方法的集合。3. 动态库和静态库3.1 静态库 .a结尾libxxx.a)静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了其后缀名一般为“.a”。3.2 动态库 .so结尾(libxxx.so)动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时再链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。和动态库产生关联的过程叫做动态链接一旦库缺失所有相关程序都运行不了。动态链接的是关联关系是通过地址完成的。编译程序我们通常用动态库和动态链接是最佳实践我们可以强制使用静态链接编译看看。安装静态库默认是没有安装的-static 链接成静态的静态链接大小远大于动态链接会导致内存占用过大。gcc默认生成的二进制程序是动态链接的这点可以通过[file] 命令验证。4. 自动化构建-make/Makefile上面描述的编译链接是针对某一个文件的那么如果我们有后面的工程项目中我们引入头文件、还各种源文件需要一起编译链接如果有100个那么我们是不是要一个命令一个命令的编译100次呢那是非常低效的一种方式。那么有没有什么方法能批量化的构建我们的项目呢那么我们就引入了自动化构建方式–make。构建编译链接运行makefile带来的好处就是—“自动化编译”一旦写好只需要一个make命令整个工程完全自动编译极大的提高了软件开发的效率。4.1 写一个最简单的make/makefilemake是一条命令makefile是一个文件make是一个命令工具是一个解释makefile 中指令的命令工具一般来说大多数的IDE都有这个命令比如Delphi的makeVisual C的nmakeLinux下GNU的make。可见makefile都成为了一种在工程方面的编译方法。新建一个文件Makefilemakefile也是可以的准备一个text.c打印一个HelloWordvim打开Makefile写入[make]运行该Makefile4.2 介绍依赖关系和依赖方法依赖关系和依赖方法结合必须成对且具有合理性我们人与人之间的来往也是构建在依赖关系和依赖方法上的。结合实际的运用我们可以加上一个清理的过程text.exe:text.c gcc text.c-otext.exe .PHONY:clean clean:rm-ftext.exe text.i text.s text.o我们可以总结出四条结论依赖关系必须存在但是依赖文件列表可以为空依赖方法可以是任何shell命令clean目标只是利用make的自动推导能力让他执行rm命令在构建工程的视角看起来就是清理项目但是其本质是清理不需要的临时文件。make后面可以跟目标名字后面跟着谁就解析谁的依赖关系和依赖方法make默认只会推导一条完整的推导链路并且是推导第一个依赖关系对应的推导。4.3 解析 .PHONY工程是需要被清理的:像clean这种没有被第一个目标文件直接或间接关联那么它后面所定义的命令将不会被自动执行。不过我们可以显示要make执行。即命令—“make clean”以此来清除所有的目标文件以便重编译。但是一般我们这种clean的目标文件我们将它设置为伪目标,用.PHONY 修饰,伪目标的特性是总是被执行的。什么叫做总是被执行文件内容属性Modify内容变更时间更新Change属性变更时间更新Access文件最近被访问的时间什么叫访问catstat都叫访问文件一个文件内容被修改是要从内存刷新到磁盘的更新的内容都要刷新到磁盘上查看要比修改的次数多。输出结论.PHONY让目标总是执行的根本原因在于让make忽略源文件和可执行目标文件的时间对比补充touch可以修改文件所对应的事件4.3.1 .PHONY的本质.PHONY 是 Makefile 中的一个特殊目标声明它的作用是告诉 make这个目标不是真实文件不要检查它是否存在也不要比较它的时间戳每次都直接执行它所对应的命令。4.3.2 为什么需要.PHONYmake 的默认逻辑是根据“目标和依赖文件的修改时间”来决定是否执行命令。如果目标文件已存在且比所有依赖文件都新就跳过执行。对于 clean 这类目标它的目的不是生成一个叫 clean的文件而是执行删除操作。如果没有 .PHONY 声明一旦当前目录下恰好有一个名为 clean的文件make 就会认为这个目标“已经满足条件”直接跳过导致清理命令根本不执行。4.3.3 为什么普通编译目标不用.PHONY像 text.exe这种目标需要生成真实文件它的执行必须依赖“文件时间戳”来判断是否需要更新——这样才支持增量编译即只重新编译改过的文件没改过的不动从而提高构建效率。如果给 text.exe加上 .PHONY每次都会强制重链反而浪费性能。4.4.4 .PHONY的底层原理make 在解析 Makefile 时会把 .PHONY 声明的目标标记为“非文件目标”。在执行阶段make 会直接跳过文件存在性检查和时间戳比较无条件执行命令。因此这种目标总是被执行——它是被规则驱动而不是被文件驱动。4.5 总结推导过程下面我们逐步拆解上面text.c-text.exe的过程。text.exe: text.o gcc-otext.exe text.o text.o: text.s gcc-ctext.s-otext.o text.s: text.i gcc-Stext.i-otext.s text.i: text.c gcc-Etext.c-otext.imake执行make命令会逐一解析makefile,会形成推导栈推导栈其实就是依赖关系的集合。总结make是如何工作的make会在当前目录下找名字叫“Makefile”或“makefile”的文件。如果找到它会找文件中的第一个目标文件target在上面的例子中他会找到text.exe这个文件并把这个文件作为最终的目标文件。如果该文件不存在或者说是text.exe所依赖的后面的text.o文件的文件修改时间要比text.exe这个文件新可以用touch测试那么他就会执行后面所定义的命令来生成text.exe这个文件。如果text.exe所依赖的text .o文件不存在那么make 会在当前文件中找目标为text .o 文件的依赖性如果找到则再根据那一个规则生成text.o 文件。这有点像一个堆栈的过程你的C文件和H文件是存在的于是make 会生成text .o 文件然后再用text.o 文件声明make 的终极任务也就是执行文件hello 了这就是整个make 的依赖性make会一层又一层地去找文件的依赖关系直到最终编译出第一个目标文件。在找寻的过程中如果出现错误比如最后被依赖的文件找不到那么make就会直接退出并报错而对于所定义的命令的错误或是编译不成功make是不会理的。make只管文件的依赖性即如果在我找了依赖关系之后冒号后面的文件还是不在那么make就不工作了。4.6 扩展makefile的语法make会将命令回显回来怎样不回显命令?每个命令前面写形成可执行还要清理或重新生成。2. 如果我有一天想要修改文件名那么通篇都需要改这样很不友好。那么我们可以BINtext.exe SRCtext.c $(BIN):$(SRC) echo 开始编译代码 gcc -o $ $^ echo 编译完成 .PHONY:clean clean: echo 清理工程。。。 rm -f $(BIN) echo 清理完毕。。。从此往后改就只需要改最上面就可以了。3. 要是我们的项目中出现了100个源文件我们应该如何编译首先建立100个源文件touchsrc{1..100}.c同时这批文件还可以批量删除rmsrc{1..100}.c我们不可能将上述文件一个一个编译修改makefile小细节下述二者都能生成.o文件gcc -c src1.c会自动生成 src1.o省略了 -o参数更简洁。gcc-csrc1.c-osrc1.o gcc-csrc1.cMakefile的最佳实践可以解决绝大部分的自动化构建问题BINtext.exe #SRC$(shell ls *.c)#方法一 SRC$(wildcard *.c)#wildcard函数获取当前目录下的所有原文件 OBJ$(SRC:.c.o)#将.c换成.o CCgcc #定义变量或者指令便于后期替换 Echoecho Rmrm -f $(BIN):$(OBJ) $(CC) -o $ $^ #交代一下各个.o文件应该怎么编.o文件当前并不存在 $(Echo) linking $^ to $...done %.o:%.c $(CC) -c $ #表示第一个依赖文件 $(Echo) compling $ to $...done .PHONY:clean clean: $(Rm) $(OBJ) $(BIN) .PHONY:text text: $(Echo) Debug--- $(Echo) $(SRC); $(Echo) Debug--- $(Echo) $(OBJ); $(Echo) Debug---欢迎大家批评指正