3. 编译器gcc/g3-1背景知识1. 预处理进行宏替换/去注释/条件编译/头文件展开等)2. 编译生成汇编)3. 汇编生成机器可识别代码4. 连接生成可执行文件或库文件)3-2 gcc编译选项格式 gcc [选项] 要编译的文件 [选项] [目标文件]3-2-1 预处理(进行宏替换)• 预处理功能主要包括宏定义,文件包含,条件编译,去注释等。• 预处理指令是以#号开头的代码行。• 实例: gcc –E hello.c –o hello.i :• 选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程。• 选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序。3-2-2 编译生成汇编• 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作, 在检查无误后,gcc 把代码翻译成汇编语言。• 用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。• 实例: gcc –S hello.i –o hello.s3-2-3 汇编生成机器可识别代码• 汇编阶段是把编译阶段生成的“.s”文件转成目标文件• 在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了• 实例: gcc –c hello.s –o hello.o3-2-4 连接生成可执行文件或库文件• 在成功编译之后,就进入了链接阶段。• 实例: gcc hello.o –o hello3-3 动态链接和静态链接在我们的实际开发中不可能将所有代码放在一个源文件中所以会出现多个源文件而且多个源文 件之间不是独立的而会存在多种依赖关系如一个源文件可能要调用另一个源文件中定义的函数 但是每个源文件都是独立编译的即每个*.c文件会形成一个*.o文件为了满足前面说的依赖关系则需要将这些源文件产生的目标文件进行链接从而形成一个可以执行的程序。这个链接的过程就是静态链接。静态链接的缺点很明显• 浪费空间因为每个可执行程序中对所有需要的目标文件都要有一份副本所以如果多个程序对 同一个目标文件都有依赖如多个程序中都调用了printf()函数则这多个程序中都含有 printf.o所以同一个目标文件都在内存存在多个副本• 更新比较困难因为每当库函数的代码修改了这个时候就需要重新进行编译链接形成可执行程 序。但是静态链接的优点就是在可执行程序中已经具备了所有执行程序所需要的任何东西在 执行的时候运行速度快。动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对 独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。动态链接其实远比静态链接要常用得多。在这里涉及到一个重要的概念: 库• 我们的C程序中并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该 函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?• 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定 时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样 就能实现函数“printf”了,而这也就是链接的作用3-4 静态库和动态库• 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运 行时也就不再需要库文件了。其后缀名一般为“.a”• 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由 运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文 件,如下所示。gcc hello.o –o hello• gcc默认生成的二进制程序是动态链接的这点可以通过 file 命令验证。注意• Linux下动态库XXX.so, 静态库XXX.a• Windows下动态库XXX.dll, 静态库XXX.lib3-5 gcc其他常用选项4. 自动化构建-make/Makefile4-1 背景• 会不会写makefile从一个侧面说明了一个人是否具备完成大型工程的能力• 一个工程中的源文件不计数其按类型、功能、模块分别放在若干个目录中makefile定义了一 系列的规则来指定哪些文件需要先编译哪些文件需要后编译哪些文件需要重新编译甚至 于进行更复杂的功能操作• makefile带来的好处就是——“自动化编译”一旦写好只需要一个make命令整个工程完全 自动编译极大的提高了软件开发的效率。•make是一个命令工具是一个解释makefile中指令的命令工具一般来说大多数的IDE都有这 个命令比如Delphi的makeVisual C的nmakeLinux下GNU的make。可见makefile 都成为了一种在工程方面的编译方法。•make是一条命令makefile是一个文件两个搭配使用完成项目自动化构建。4-2 基本使用make一下然后执行程序简单介绍一下关于makefiletest:test.c gcc -o test test.c .PHONY:clean clean: rm -f test依赖关系• 上面的文件test,它依赖test.c依赖方法• gcc -o test test.c ,就是与之对应的依赖关系项目清理 • 工程是需要被清理的• 像clean这种没有被第一个目标文件直接或间接关联那么它后面所定义的命令将不会被自动 执行不过我们可以显示要make执行。即命令——“make clean”以此来清除所有的目标 文件以便重编译。• 但是一般我们这种clean的目标文件我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性 是总是被执行的。.PHONY:让make忽略源文件和可执行目标文件的M时间对比• 可以将我们的 test目标文件声明成伪目标测试一下。可以看到make clean后test文件被删除了4-3 推导过程makefile内容如下编译• make是如何工作的,在默认的方式下也就是我们只输入make命令。那么 1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。2. 如果找到它会找文件中的第一个目标文件target在上面的例子中他会找到 myproc 这 个文件并把这个文件作为最终的目标文件。3. 如果 myproc 文件不存在或是 myproc 所依赖的后面的 myproc.o 文件的文件修改时间要 比 myproc 这个文件新可以用 touch 测试那么他就会执行后面所定义的命令来生成 myproc 这个文件。4. 如果 myproc 所依赖的 myproc.o 文件不存在那么 make 会在当前文件中找目标为 myproc.o 文件的依赖性如果找到则再根据那一个规则生成 myproc.o 文件。这有点像一 个堆栈的过程5. 当然你的C文件和H文件是存在的啦于是 make 会生成 myproc.o 文件然后再用 myproc.o 文件声明 make 的终极任务也就是执行文件 hello 了。6. 这就是整个make的依赖性make会一层又一层地去找文件的依赖关系直到最终编译出第一个 目标文件。7. 在找寻的过程中如果出现错误比如最后被依赖的文件找不到那么make就会直接退出并 报错而对于所定义的命令的错误或是编译不成功make根本不理。8. make只管文件的依赖性即如果在我找了依赖关系之后冒号后面的文件还是不在那么就不工作啦。明白了代码字体格式保持原来那种代码块效果不变只把标题层级改掉4-4、4-5、4-6、4-7、4-8 改成二级标题##它们下面的 4-4-1、4-4-2 这种改成三级标题###内容不精简下面是修改后的版本。4-4 Makefile 的基本语法前面我们已经知道make 是一个自动化构建工具而 Makefile 就是告诉 make哪些文件需要生成这些文件依赖哪些文件生成它们时要执行哪些命令也就是说Makefile 本质上是在描述目标、依赖、命令三者之间的关系。4-4-1 Makefile 的基本格式一个最基本的 Makefile 规则格式如下目标文件: 依赖文件 生成目标文件所需要执行的命令也可以写成更通用的形式target: dependencies command其中target 表示目标文件也就是我们最终想要生成的文件dependencies 表示依赖文件也就是生成目标文件需要用到哪些文件command 表示命令也就是如何根据依赖文件生成目标文件例如myproc: myproc.c gcc myproc.c -o myproc这里的含义是myproc 是目标文件myproc.c 是依赖文件gcc myproc.c -o myproc 是生成目标文件的命令也就是说如果想得到 myproc就需要依赖 myproc.c然后执行下面的 gcc 命令。这条 Makefile 规则实际上等价于我们手动在命令行中输入gcc myproc.c -o myproc但是使用 Makefile 之后我们就不需要每次手动输入这一长串命令了只需要执行make即可。这里有一个非常重要的细节Makefile 中命令前面必须是 Tab 键不能是普通空格。例如myproc: myproc.c gcc myproc.c -o myproc第二行 gcc 前面必须是一个 Tab。如果写成普通空格make 很可能会报错Makefile:2: *** missing separator. Stop.这个错误对于初学者来说非常常见所以一定要注意。4-4-2 一个最简单的 Makefile假设我们有一个源文件 test.c#include stdio.h int main() { printf(hello makefile\n); return 0; }如果不使用 Makefile我们平时手动编译会这样写gcc test.c -o test如果使用 Makefile可以这样写test: test.c gcc test.c -o test然后在命令行中直接输入makemake 会自动寻找当前目录下的 Makefile 或 makefile 文件然后执行其中的规则。执行结果就相当于帮我们运行了gcc test.c -o test编译完成之后当前目录下就会生成一个可执行程序test然后我们就可以运行./test输出结果hello makefile所以最简单的 Makefile 其实就是把我们平时手动输入的编译命令写到文件里面让 make 帮我们执行。4-4-3 Makefile 文件名make 默认会在当前目录下寻找下面几个文件名GNUmakefile makefile Makefile一般情况下我们最常用的是Makefile注意首字母大写。所以我们通常会这样创建文件touch Makefile然后把规则写进去。当我们执行makemake 就会自动读取当前目录下的 Makefile。当然也可以使用 -f 指定 Makefile 文件名make -f mymakefile不过初学阶段一般不用这么写直接使用 Makefile 这个文件名即可。4-5 Makefile 的执行逻辑Makefile 看起来只是几行规则但它并不是简单地从上往下全部执行。make 真正厉害的地方在于它会根据目标、依赖和文件时间戳判断哪些命令需要执行哪些命令不需要执行。4-5-1 make 默认执行第一个目标例如有这样一个 Makefilemyproc: myproc.c gcc myproc.c -o myproc clean: rm -f myproc当我们直接输入make它会默认执行第一个目标myproc所以会生成可执行程序 myproc。如果我们想执行 clean需要显式指定make clean这样 make 才会执行clean: rm -f myproc因此可以总结make 默认执行 Makefile 中的第一个目标。所以一般来说我们会把最终目标放在 Makefile 的最前面。比如我们最终想生成 myproc就把它写在第一条规则myproc: myproc.c gcc myproc.c -o myproc这样直接执行 make 时就能生成最终程序。4-5-2 依赖关系与时间戳make 的强大之处在于它会根据文件的修改时间判断是否需要重新编译。例如myproc: myproc.c gcc myproc.c -o myproc第一次执行make因为当前目录下还没有 myproc 这个目标文件所以 make 会执行命令gcc myproc.c -o myproc于是生成了可执行程序 myproc。如果此时我们再次执行make可能会看到make: myproc is up to date.意思是目标文件 myproc 已经是最新的了不需要重新编译。那么 make 是怎么判断的呢它会比较目标文件 myproc 的修改时间依赖文件 myproc.c 的修改时间如果 myproc.c 比 myproc 更新说明源文件被修改过就需要重新编译。如果 myproc 比 myproc.c 更新说明目标文件已经是最新的就不用重新编译。所以 make 的核心思想是如果目标文件不存在或者依赖文件比目标文件更新就执行对应命令。换句话说目标不存在执行命令 依赖更新了执行命令 目标比依赖更新不执行命令这也是为什么 make 可以提高编译效率。它不会每次都全部重新编译而是只重新编译真正需要更新的部分。4-5-3 添加 clean 清理规则在开发过程中我们经常需要删除编译生成的文件。比如删除可执行程序rm -f myproc如果每次都手动输入也比较麻烦。我们可以把它写进 Makefilemyproc: myproc.c gcc myproc.c -o myproc clean: rm -f myproc这样以后只需要执行make clean就可以清理编译结果。这里的 clean 并不是一个真正需要生成的文件而是一个动作clean 表示清理项目不过这里有一个问题。如果当前目录下刚好有一个文件也叫 clean那么执行make cleanmake 可能会认为目标 clean 已经存在并且是最新的于是不会执行清理命令。所以我们通常会把 clean 声明成伪目标。4-5-4 伪目标 .PHONY伪目标的意思是这个目标不是真的要生成一个文件而只是一个命令名称。写法如下.PHONY: clean完整写法myproc: myproc.c gcc myproc.c -o myproc .PHONY: clean clean: rm -f myproc这样无论当前目录下有没有 clean 这个文件执行make clean都会执行清理命令。常见的伪目标还有.PHONY: clean all其中clean 通常用于清理生成文件all 通常用于表示最终要生成的所有目标例如.PHONY: all clean all: myproc myproc: myproc.c gcc myproc.c -o myproc clean: rm -f myproc这里的 all 也是一个伪目标它本身不生成文件只是依赖 myproc。执行make由于第一个目标是 all所以 make 会先构建 all而 all 依赖 myproc于是最终会生成 myproc。执行make clean则会清理生成的程序。4-6 Makefile 中的变量和自动化变量当 Makefile 变复杂之后如果还一直重复写文件名和命令就会非常麻烦。例如myproc: myproc.c gcc myproc.c -o myproc这里 myproc 出现了两次myproc.c 也出现了两次。如果以后想改程序名就需要到处改。所以 Makefile 支持变量。4-6-1 普通变量Makefile 中定义变量的基本格式变量名变量值例如CCgcc TARGETmyproc SRCmyproc.c使用变量时要写成$(变量名)例如$(CC) $(TARGET) $(SRC)所以原来的 Makefilemyproc: myproc.c gcc myproc.c -o myproc可以改成CCgcc TARGETmyproc SRCmyproc.c $(TARGET): $(SRC) $(CC) $(SRC) -o $(TARGET)展开之后就相当于myproc: myproc.c gcc myproc.c -o myproc这样写的好处是如果以后想把目标文件名从 myproc 改成 test只需要改变量TARGETtest不用到处修改命令。完整 Makefile 可以写成CCgcc TARGETmyproc SRCmyproc.c $(TARGET): $(SRC) $(CC) $(SRC) -o $(TARGET) .PHONY: clean clean: rm -f $(TARGET)4-6-2 常见变量名虽然 Makefile 中变量名可以自己定义但是实际开发中有一些比较常见的习惯写法。CCgcc CXXg CFLAGS-Wall -g CXXFLAGS-Wall -g TARGETmyproc SRCSmain.c add.c sub.c OBJSmain.o add.o sub.o它们大概表示CCC 编译器一般是 gccCXXC 编译器一般是 gCFLAGSC 编译选项CXXFLAGSC 编译选项TARGET最终生成的目标程序SRCS源文件列表OBJS目标文件列表也就是 .o 文件列表例如CCgcc CFLAGS-Wall -g TARGETmyproc SRCmyproc.c $(TARGET): $(SRC) $(CC) $(CFLAGS) $(SRC) -o $(TARGET)这里CFLAGS-Wall -g表示编译时加上两个选项-Wall表示打开常见警告。-g表示生成调试信息方便使用 gdb 调试。展开后就是gcc -Wall -g myproc.c -o myproc4-6-3 自动化变量 $、$^、$Makefile 中还有一些特殊变量它们不需要我们自己定义而是 make 自动生成的所以叫自动化变量。常用的有三个$ 表示目标文件 $^ 表示所有依赖文件 $ 表示第一个依赖文件1. $ 表示目标文件例如myproc: myproc.c gcc myproc.c -o myproc可以写成myproc: myproc.c gcc myproc.c -o $这里 $ 就表示当前规则的目标也就是myproc所以命令展开后就是gcc myproc.c -o myproc2. $^ 表示所有依赖文件例如myproc: main.o add.o sub.o gcc main.o add.o sub.o -o myproc可以写成myproc: main.o add.o sub.o gcc $^ -o $这里$^代表所有依赖文件main.o add.o sub.o而$代表目标文件myproc所以命令展开后就是gcc main.o add.o sub.o -o myproc3. $ 表示第一个依赖文件例如main.o: main.c gcc -c main.c -o main.o可以写成main.o: main.c gcc -c $ -o $这里$ 表示第一个依赖文件也就是 main.c$ 表示目标文件也就是 main.o所以命令展开后就是gcc -c main.c -o main.o注意$ 只表示第一个依赖文件。如果规则是myproc: main.o add.o sub.o gcc $ -o $那么 $ 只表示main.o不会表示后面的 add.o sub.o。所以链接多个 .o 文件时一般用 $^而不是 $。4-7 多文件项目的 Makefile 编写实际项目中一般不会只有一个 .c 文件。如果项目中有多个源文件我们就需要写稍微完整一点的 Makefile。4-7-1 直接编译多个源文件假设有三个文件main.c add.c sub.c如果手动编译可以这样写gcc main.c add.c sub.c -o myproc对应的 Makefile 可以写成myproc: main.c add.c sub.c gcc main.c add.c sub.c -o myproc这个写法能用但不够好。因为每次只要有一个 .c 文件修改都会把所有 .c 文件重新编译一遍。例如我们只修改了 add.c然后执行makemake 会重新执行gcc main.c add.c sub.c -o myproc这样 main.c 和 sub.c 也会跟着重新编译。在小项目中影响不大但是如果项目很大编译速度就会变慢。所以更推荐的方式是先把每个 .c 文件编译成 .o 文件再把所有 .o 文件链接成最终程序。4-7-2 使用 .o 文件进行分步编译C 程序的构建一般可以分为两步第一步编译源文件生成目标文件 .ogcc -c main.c -o main.o gcc -c add.c -o add.o gcc -c sub.c -o sub.o这里的 -c 表示只编译不链接。第二步链接所有 .o 文件生成可执行程序gcc main.o add.o sub.o -o myproc对应 Makefilemyproc: main.o add.o sub.o gcc main.o add.o sub.o -o myproc main.o: main.c gcc -c main.c -o main.o add.o: add.c gcc -c add.c -o add.o sub.o: sub.c gcc -c sub.c -o sub.o .PHONY: clean clean: rm -f myproc main.o add.o sub.o这个 Makefile 的构建过程是1. make 想生成 myproc 2. 发现 myproc 依赖 main.o add.o sub.o 3. 如果这些 .o 文件不存在就先生成它们 4. main.o 由 main.c 编译得到 5. add.o 由 add.c 编译得到 6. sub.o 由 sub.c 编译得到 7. 三个 .o 文件都准备好后再链接成 myproc这样做的好处是如果只修改了 add.c那么下次执行makemake 只会重新生成add.o然后再重新链接生成myproc不会重新编译 main.c 和 sub.c。这就是 make 提高编译效率的关键。4-7-3 使用变量优化多文件 Makefile前面的 Makefilemyproc: main.o add.o sub.o gcc main.o add.o sub.o -o myproc main.o: main.c gcc -c main.c -o main.o add.o: add.c gcc -c add.c -o add.o sub.o: sub.c gcc -c sub.c -o sub.o .PHONY: clean clean: rm -f myproc main.o add.o sub.o可以用变量优化CCgcc TARGETmyproc OBJSmain.o add.o sub.o $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) main.o: main.c $(CC) -c main.c -o main.o add.o: add.c $(CC) -c add.c -o add.o sub.o: sub.c $(CC) -c sub.c -o sub.o .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这样一来如果目标文件名改了只需要修改TARGETmyproc如果 .o 文件列表变了只需要修改OBJSmain.o add.o sub.o4-7-4 使用自动化变量继续优化上面的写法还可以继续优化。原来$(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET)可以写成$(TARGET): $(OBJS) $(CC) $^ -o $因为$^ 表示所有依赖文件也就是 $(OBJS)$ 表示目标文件也就是 $(TARGET)原来main.o: main.c $(CC) -c main.c -o main.o可以写成main.o: main.c $(CC) -c $ -o $因为$ 表示第一个依赖文件也就是 main.c$ 表示目标文件也就是 main.o完整 MakefileCCgcc TARGETmyproc OBJSmain.o add.o sub.o $(TARGET): $(OBJS) $(CC) $^ -o $ main.o: main.c $(CC) -c $ -o $ add.o: add.c $(CC) -c $ -o $ sub.o: sub.c $(CC) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这样已经比最初的写法简洁很多了。4-7-5 模式规则虽然上面的写法已经不错但还有重复main.o: main.c $(CC) -c $ -o $ add.o: add.c $(CC) -c $ -o $ sub.o: sub.c $(CC) -c $ -o $这些规则的结构完全一样.c 文件生成对应的 .o 文件。所以可以使用模式规则。写法如下%.o: %.c $(CC) -c $ -o $这里的 % 可以理解成通配符。它表示相同的文件名前缀。例如%.o: %.c可以匹配main.o: main.c add.o: add.c sub.o: sub.c也就是说main.o 由 main.c 生成add.o 由 add.c 生成sub.o 由 sub.c 生成于是 Makefile 可以写成CCgcc TARGETmyproc OBJSmain.o add.o sub.o $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这就是一个比较常见的 Makefile 写法。4-8 Makefile 的最终推荐模板前面我们一步一步从最简单的 Makefile讲到了变量、自动化变量、多文件编译和模式规则。最后我们整理出一个比较实用的模板。4-8-1 C 语言项目 Makefile 模板CCgcc CFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.c) OBJS$(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这个模板中CCgcc表示使用 gcc 编译器。CFLAGS-Wall -g表示编译选项。其中-Wall 表示开启常见警告-g 表示生成调试信息方便 gdb 调试TARGETmyproc表示最终生成的可执行程序叫 myproc。SRCS$(wildcard *.c)表示获取当前目录下所有 .c 文件。例如当前目录下有main.c add.c sub.c那么$(wildcard *.c)展开后就是main.c add.c sub.cOBJS$(SRCS:.c.o)表示把 SRCS 中所有 .c 后缀替换成 .o 后缀。也就是说main.c add.c sub.c会变成main.o add.o sub.o$(TARGET): $(OBJS) $(CC) $^ -o $表示最终目标 myproc 依赖所有 .o 文件。链接时$^ 表示所有依赖文件也就是所有 .o$ 表示目标文件也就是 myproc%.o: %.c $(CC) $(CFLAGS) -c $ -o $表示所有 .o 文件都可以由同名 .c 文件编译得到。其中$ 表示第一个依赖文件也就是对应的 .c$ 表示目标文件也就是对应的 .o.PHONY: clean clean: rm -f $(TARGET) $(OBJS)表示清理生成的可执行程序和 .o 文件。使用时只需要执行make即可编译项目。执行make clean即可清理项目。以后新增一个 .c 文件时不需要修改 Makefile。比如新增mul.c再次执行make它会自动被编译进去。4-8-2 C 项目 Makefile 模板如果是 C 项目可以改成CXXg CXXFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.cc) OBJS$(SRCS:.cc.o) $(TARGET): $(OBJS) $(CXX) $^ -o $ %.o: %.cc $(CXX) $(CXXFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)如果你的 C 源文件后缀是 .cpp就写成CXXg CXXFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.cpp) OBJS$(SRCS:.cpp.o) $(TARGET): $(OBJS) $(CXX) $^ -o $ %.o: %.cpp $(CXX) $(CXXFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这里和 C 语言模板的区别主要是C 语言一般使用 gccC 一般使用 gC 编译选项习惯写成 CFLAGSC 编译选项习惯写成 CXXFLAGS4-8-3 一个完整的使用过程假设当前目录下有这些文件main.c add.c sub.c MakefileMakefile 内容如下CCgcc CFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.c) OBJS$(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)第一次执行makemake 会做这些事情1. 找到默认目标 myproc 2. 发现 myproc 依赖 main.o add.o sub.o 3. 发现 main.o add.o sub.o 不存在 4. 根据 %.o: %.c 规则分别编译 main.c add.c sub.c 5. 生成 main.o add.o sub.o 6. 最后链接这几个 .o 文件生成 myproc然后可以运行./myproc如果只修改了 add.c再次执行makemake 会做这些事情1. 发现 add.c 比 add.o 新 2. 所以重新编译 add.c生成新的 add.o 3. 发现 add.o 比 myproc 新 4. 所以重新链接生成新的 myproc 5. main.c 和 sub.c 没有修改所以 main.o 和 sub.o 不会重新生成如果想清理make clean它会执行rm -f myproc main.o add.o sub.o4-8-4 总结Makefile 的核心并不复杂重点记住三件事。第一基本格式目标: 依赖 命令第二make 会根据依赖关系 文件时间戳判断是否需要重新执行命令。第三常用自动化变量$ 表示目标文件 $^ 表示所有依赖文件 $ 表示第一个依赖文件初学时可以先掌握这个模板CCgcc CFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.c) OBJS$(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)以后写小型 C 项目时基本就可以直接套用。只需要修改TARGETmyproc就能生成自己想要的可执行程序名BINproc.exe # 定义变量 CCgcc #SRC$(shell ls *.c) # 采用shell命令行方式获取当前所有.c文件名 SRC$(wildcard *.c) # 或者使用 wildcard 函数获取当前所有.c文件名 OBJ$(SRC:.c.o) # 将SRC的所有同名.c 替换 成为.o 形成目标文件列表 LFLAGS-o # 链接选项 FLAGS-c # 编译选项 RMrm -f # 引入命令 $(BIN):$(OBJ) $(CC) $(LFLAGS) $ $^ # $:代表目标文件名。 $^: 代表依赖文件列表 echo linking ... $^ to $ %.o:%.c # %.c 展开当前目录下所有的.c。 %.o: 同时展开同名.o $(CC) $(FLAGS) $ # %: 对展开的依赖.c文件一个一个的交给gcc。 echo compling ... $ to $ # 不回显命令 .PHONY:clean clean: $(RM) $(OBJ) $(BIN) # $(RM): 替换用变量内容替换它 .PHONY:test test: echo $(SRC) echo $(OBJ)
二、Linux基础开发工具(2)
3. 编译器gcc/g3-1背景知识1. 预处理进行宏替换/去注释/条件编译/头文件展开等)2. 编译生成汇编)3. 汇编生成机器可识别代码4. 连接生成可执行文件或库文件)3-2 gcc编译选项格式 gcc [选项] 要编译的文件 [选项] [目标文件]3-2-1 预处理(进行宏替换)• 预处理功能主要包括宏定义,文件包含,条件编译,去注释等。• 预处理指令是以#号开头的代码行。• 实例: gcc –E hello.c –o hello.i :• 选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程。• 选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序。3-2-2 编译生成汇编• 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作, 在检查无误后,gcc 把代码翻译成汇编语言。• 用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。• 实例: gcc –S hello.i –o hello.s3-2-3 汇编生成机器可识别代码• 汇编阶段是把编译阶段生成的“.s”文件转成目标文件• 在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了• 实例: gcc –c hello.s –o hello.o3-2-4 连接生成可执行文件或库文件• 在成功编译之后,就进入了链接阶段。• 实例: gcc hello.o –o hello3-3 动态链接和静态链接在我们的实际开发中不可能将所有代码放在一个源文件中所以会出现多个源文件而且多个源文 件之间不是独立的而会存在多种依赖关系如一个源文件可能要调用另一个源文件中定义的函数 但是每个源文件都是独立编译的即每个*.c文件会形成一个*.o文件为了满足前面说的依赖关系则需要将这些源文件产生的目标文件进行链接从而形成一个可以执行的程序。这个链接的过程就是静态链接。静态链接的缺点很明显• 浪费空间因为每个可执行程序中对所有需要的目标文件都要有一份副本所以如果多个程序对 同一个目标文件都有依赖如多个程序中都调用了printf()函数则这多个程序中都含有 printf.o所以同一个目标文件都在内存存在多个副本• 更新比较困难因为每当库函数的代码修改了这个时候就需要重新进行编译链接形成可执行程 序。但是静态链接的优点就是在可执行程序中已经具备了所有执行程序所需要的任何东西在 执行的时候运行速度快。动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对 独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。动态链接其实远比静态链接要常用得多。在这里涉及到一个重要的概念: 库• 我们的C程序中并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该 函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?• 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定 时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样 就能实现函数“printf”了,而这也就是链接的作用3-4 静态库和动态库• 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运 行时也就不再需要库文件了。其后缀名一般为“.a”• 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由 运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文 件,如下所示。gcc hello.o –o hello• gcc默认生成的二进制程序是动态链接的这点可以通过 file 命令验证。注意• Linux下动态库XXX.so, 静态库XXX.a• Windows下动态库XXX.dll, 静态库XXX.lib3-5 gcc其他常用选项4. 自动化构建-make/Makefile4-1 背景• 会不会写makefile从一个侧面说明了一个人是否具备完成大型工程的能力• 一个工程中的源文件不计数其按类型、功能、模块分别放在若干个目录中makefile定义了一 系列的规则来指定哪些文件需要先编译哪些文件需要后编译哪些文件需要重新编译甚至 于进行更复杂的功能操作• makefile带来的好处就是——“自动化编译”一旦写好只需要一个make命令整个工程完全 自动编译极大的提高了软件开发的效率。•make是一个命令工具是一个解释makefile中指令的命令工具一般来说大多数的IDE都有这 个命令比如Delphi的makeVisual C的nmakeLinux下GNU的make。可见makefile 都成为了一种在工程方面的编译方法。•make是一条命令makefile是一个文件两个搭配使用完成项目自动化构建。4-2 基本使用make一下然后执行程序简单介绍一下关于makefiletest:test.c gcc -o test test.c .PHONY:clean clean: rm -f test依赖关系• 上面的文件test,它依赖test.c依赖方法• gcc -o test test.c ,就是与之对应的依赖关系项目清理 • 工程是需要被清理的• 像clean这种没有被第一个目标文件直接或间接关联那么它后面所定义的命令将不会被自动 执行不过我们可以显示要make执行。即命令——“make clean”以此来清除所有的目标 文件以便重编译。• 但是一般我们这种clean的目标文件我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性 是总是被执行的。.PHONY:让make忽略源文件和可执行目标文件的M时间对比• 可以将我们的 test目标文件声明成伪目标测试一下。可以看到make clean后test文件被删除了4-3 推导过程makefile内容如下编译• make是如何工作的,在默认的方式下也就是我们只输入make命令。那么 1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。2. 如果找到它会找文件中的第一个目标文件target在上面的例子中他会找到 myproc 这 个文件并把这个文件作为最终的目标文件。3. 如果 myproc 文件不存在或是 myproc 所依赖的后面的 myproc.o 文件的文件修改时间要 比 myproc 这个文件新可以用 touch 测试那么他就会执行后面所定义的命令来生成 myproc 这个文件。4. 如果 myproc 所依赖的 myproc.o 文件不存在那么 make 会在当前文件中找目标为 myproc.o 文件的依赖性如果找到则再根据那一个规则生成 myproc.o 文件。这有点像一 个堆栈的过程5. 当然你的C文件和H文件是存在的啦于是 make 会生成 myproc.o 文件然后再用 myproc.o 文件声明 make 的终极任务也就是执行文件 hello 了。6. 这就是整个make的依赖性make会一层又一层地去找文件的依赖关系直到最终编译出第一个 目标文件。7. 在找寻的过程中如果出现错误比如最后被依赖的文件找不到那么make就会直接退出并 报错而对于所定义的命令的错误或是编译不成功make根本不理。8. make只管文件的依赖性即如果在我找了依赖关系之后冒号后面的文件还是不在那么就不工作啦。明白了代码字体格式保持原来那种代码块效果不变只把标题层级改掉4-4、4-5、4-6、4-7、4-8 改成二级标题##它们下面的 4-4-1、4-4-2 这种改成三级标题###内容不精简下面是修改后的版本。4-4 Makefile 的基本语法前面我们已经知道make 是一个自动化构建工具而 Makefile 就是告诉 make哪些文件需要生成这些文件依赖哪些文件生成它们时要执行哪些命令也就是说Makefile 本质上是在描述目标、依赖、命令三者之间的关系。4-4-1 Makefile 的基本格式一个最基本的 Makefile 规则格式如下目标文件: 依赖文件 生成目标文件所需要执行的命令也可以写成更通用的形式target: dependencies command其中target 表示目标文件也就是我们最终想要生成的文件dependencies 表示依赖文件也就是生成目标文件需要用到哪些文件command 表示命令也就是如何根据依赖文件生成目标文件例如myproc: myproc.c gcc myproc.c -o myproc这里的含义是myproc 是目标文件myproc.c 是依赖文件gcc myproc.c -o myproc 是生成目标文件的命令也就是说如果想得到 myproc就需要依赖 myproc.c然后执行下面的 gcc 命令。这条 Makefile 规则实际上等价于我们手动在命令行中输入gcc myproc.c -o myproc但是使用 Makefile 之后我们就不需要每次手动输入这一长串命令了只需要执行make即可。这里有一个非常重要的细节Makefile 中命令前面必须是 Tab 键不能是普通空格。例如myproc: myproc.c gcc myproc.c -o myproc第二行 gcc 前面必须是一个 Tab。如果写成普通空格make 很可能会报错Makefile:2: *** missing separator. Stop.这个错误对于初学者来说非常常见所以一定要注意。4-4-2 一个最简单的 Makefile假设我们有一个源文件 test.c#include stdio.h int main() { printf(hello makefile\n); return 0; }如果不使用 Makefile我们平时手动编译会这样写gcc test.c -o test如果使用 Makefile可以这样写test: test.c gcc test.c -o test然后在命令行中直接输入makemake 会自动寻找当前目录下的 Makefile 或 makefile 文件然后执行其中的规则。执行结果就相当于帮我们运行了gcc test.c -o test编译完成之后当前目录下就会生成一个可执行程序test然后我们就可以运行./test输出结果hello makefile所以最简单的 Makefile 其实就是把我们平时手动输入的编译命令写到文件里面让 make 帮我们执行。4-4-3 Makefile 文件名make 默认会在当前目录下寻找下面几个文件名GNUmakefile makefile Makefile一般情况下我们最常用的是Makefile注意首字母大写。所以我们通常会这样创建文件touch Makefile然后把规则写进去。当我们执行makemake 就会自动读取当前目录下的 Makefile。当然也可以使用 -f 指定 Makefile 文件名make -f mymakefile不过初学阶段一般不用这么写直接使用 Makefile 这个文件名即可。4-5 Makefile 的执行逻辑Makefile 看起来只是几行规则但它并不是简单地从上往下全部执行。make 真正厉害的地方在于它会根据目标、依赖和文件时间戳判断哪些命令需要执行哪些命令不需要执行。4-5-1 make 默认执行第一个目标例如有这样一个 Makefilemyproc: myproc.c gcc myproc.c -o myproc clean: rm -f myproc当我们直接输入make它会默认执行第一个目标myproc所以会生成可执行程序 myproc。如果我们想执行 clean需要显式指定make clean这样 make 才会执行clean: rm -f myproc因此可以总结make 默认执行 Makefile 中的第一个目标。所以一般来说我们会把最终目标放在 Makefile 的最前面。比如我们最终想生成 myproc就把它写在第一条规则myproc: myproc.c gcc myproc.c -o myproc这样直接执行 make 时就能生成最终程序。4-5-2 依赖关系与时间戳make 的强大之处在于它会根据文件的修改时间判断是否需要重新编译。例如myproc: myproc.c gcc myproc.c -o myproc第一次执行make因为当前目录下还没有 myproc 这个目标文件所以 make 会执行命令gcc myproc.c -o myproc于是生成了可执行程序 myproc。如果此时我们再次执行make可能会看到make: myproc is up to date.意思是目标文件 myproc 已经是最新的了不需要重新编译。那么 make 是怎么判断的呢它会比较目标文件 myproc 的修改时间依赖文件 myproc.c 的修改时间如果 myproc.c 比 myproc 更新说明源文件被修改过就需要重新编译。如果 myproc 比 myproc.c 更新说明目标文件已经是最新的就不用重新编译。所以 make 的核心思想是如果目标文件不存在或者依赖文件比目标文件更新就执行对应命令。换句话说目标不存在执行命令 依赖更新了执行命令 目标比依赖更新不执行命令这也是为什么 make 可以提高编译效率。它不会每次都全部重新编译而是只重新编译真正需要更新的部分。4-5-3 添加 clean 清理规则在开发过程中我们经常需要删除编译生成的文件。比如删除可执行程序rm -f myproc如果每次都手动输入也比较麻烦。我们可以把它写进 Makefilemyproc: myproc.c gcc myproc.c -o myproc clean: rm -f myproc这样以后只需要执行make clean就可以清理编译结果。这里的 clean 并不是一个真正需要生成的文件而是一个动作clean 表示清理项目不过这里有一个问题。如果当前目录下刚好有一个文件也叫 clean那么执行make cleanmake 可能会认为目标 clean 已经存在并且是最新的于是不会执行清理命令。所以我们通常会把 clean 声明成伪目标。4-5-4 伪目标 .PHONY伪目标的意思是这个目标不是真的要生成一个文件而只是一个命令名称。写法如下.PHONY: clean完整写法myproc: myproc.c gcc myproc.c -o myproc .PHONY: clean clean: rm -f myproc这样无论当前目录下有没有 clean 这个文件执行make clean都会执行清理命令。常见的伪目标还有.PHONY: clean all其中clean 通常用于清理生成文件all 通常用于表示最终要生成的所有目标例如.PHONY: all clean all: myproc myproc: myproc.c gcc myproc.c -o myproc clean: rm -f myproc这里的 all 也是一个伪目标它本身不生成文件只是依赖 myproc。执行make由于第一个目标是 all所以 make 会先构建 all而 all 依赖 myproc于是最终会生成 myproc。执行make clean则会清理生成的程序。4-6 Makefile 中的变量和自动化变量当 Makefile 变复杂之后如果还一直重复写文件名和命令就会非常麻烦。例如myproc: myproc.c gcc myproc.c -o myproc这里 myproc 出现了两次myproc.c 也出现了两次。如果以后想改程序名就需要到处改。所以 Makefile 支持变量。4-6-1 普通变量Makefile 中定义变量的基本格式变量名变量值例如CCgcc TARGETmyproc SRCmyproc.c使用变量时要写成$(变量名)例如$(CC) $(TARGET) $(SRC)所以原来的 Makefilemyproc: myproc.c gcc myproc.c -o myproc可以改成CCgcc TARGETmyproc SRCmyproc.c $(TARGET): $(SRC) $(CC) $(SRC) -o $(TARGET)展开之后就相当于myproc: myproc.c gcc myproc.c -o myproc这样写的好处是如果以后想把目标文件名从 myproc 改成 test只需要改变量TARGETtest不用到处修改命令。完整 Makefile 可以写成CCgcc TARGETmyproc SRCmyproc.c $(TARGET): $(SRC) $(CC) $(SRC) -o $(TARGET) .PHONY: clean clean: rm -f $(TARGET)4-6-2 常见变量名虽然 Makefile 中变量名可以自己定义但是实际开发中有一些比较常见的习惯写法。CCgcc CXXg CFLAGS-Wall -g CXXFLAGS-Wall -g TARGETmyproc SRCSmain.c add.c sub.c OBJSmain.o add.o sub.o它们大概表示CCC 编译器一般是 gccCXXC 编译器一般是 gCFLAGSC 编译选项CXXFLAGSC 编译选项TARGET最终生成的目标程序SRCS源文件列表OBJS目标文件列表也就是 .o 文件列表例如CCgcc CFLAGS-Wall -g TARGETmyproc SRCmyproc.c $(TARGET): $(SRC) $(CC) $(CFLAGS) $(SRC) -o $(TARGET)这里CFLAGS-Wall -g表示编译时加上两个选项-Wall表示打开常见警告。-g表示生成调试信息方便使用 gdb 调试。展开后就是gcc -Wall -g myproc.c -o myproc4-6-3 自动化变量 $、$^、$Makefile 中还有一些特殊变量它们不需要我们自己定义而是 make 自动生成的所以叫自动化变量。常用的有三个$ 表示目标文件 $^ 表示所有依赖文件 $ 表示第一个依赖文件1. $ 表示目标文件例如myproc: myproc.c gcc myproc.c -o myproc可以写成myproc: myproc.c gcc myproc.c -o $这里 $ 就表示当前规则的目标也就是myproc所以命令展开后就是gcc myproc.c -o myproc2. $^ 表示所有依赖文件例如myproc: main.o add.o sub.o gcc main.o add.o sub.o -o myproc可以写成myproc: main.o add.o sub.o gcc $^ -o $这里$^代表所有依赖文件main.o add.o sub.o而$代表目标文件myproc所以命令展开后就是gcc main.o add.o sub.o -o myproc3. $ 表示第一个依赖文件例如main.o: main.c gcc -c main.c -o main.o可以写成main.o: main.c gcc -c $ -o $这里$ 表示第一个依赖文件也就是 main.c$ 表示目标文件也就是 main.o所以命令展开后就是gcc -c main.c -o main.o注意$ 只表示第一个依赖文件。如果规则是myproc: main.o add.o sub.o gcc $ -o $那么 $ 只表示main.o不会表示后面的 add.o sub.o。所以链接多个 .o 文件时一般用 $^而不是 $。4-7 多文件项目的 Makefile 编写实际项目中一般不会只有一个 .c 文件。如果项目中有多个源文件我们就需要写稍微完整一点的 Makefile。4-7-1 直接编译多个源文件假设有三个文件main.c add.c sub.c如果手动编译可以这样写gcc main.c add.c sub.c -o myproc对应的 Makefile 可以写成myproc: main.c add.c sub.c gcc main.c add.c sub.c -o myproc这个写法能用但不够好。因为每次只要有一个 .c 文件修改都会把所有 .c 文件重新编译一遍。例如我们只修改了 add.c然后执行makemake 会重新执行gcc main.c add.c sub.c -o myproc这样 main.c 和 sub.c 也会跟着重新编译。在小项目中影响不大但是如果项目很大编译速度就会变慢。所以更推荐的方式是先把每个 .c 文件编译成 .o 文件再把所有 .o 文件链接成最终程序。4-7-2 使用 .o 文件进行分步编译C 程序的构建一般可以分为两步第一步编译源文件生成目标文件 .ogcc -c main.c -o main.o gcc -c add.c -o add.o gcc -c sub.c -o sub.o这里的 -c 表示只编译不链接。第二步链接所有 .o 文件生成可执行程序gcc main.o add.o sub.o -o myproc对应 Makefilemyproc: main.o add.o sub.o gcc main.o add.o sub.o -o myproc main.o: main.c gcc -c main.c -o main.o add.o: add.c gcc -c add.c -o add.o sub.o: sub.c gcc -c sub.c -o sub.o .PHONY: clean clean: rm -f myproc main.o add.o sub.o这个 Makefile 的构建过程是1. make 想生成 myproc 2. 发现 myproc 依赖 main.o add.o sub.o 3. 如果这些 .o 文件不存在就先生成它们 4. main.o 由 main.c 编译得到 5. add.o 由 add.c 编译得到 6. sub.o 由 sub.c 编译得到 7. 三个 .o 文件都准备好后再链接成 myproc这样做的好处是如果只修改了 add.c那么下次执行makemake 只会重新生成add.o然后再重新链接生成myproc不会重新编译 main.c 和 sub.c。这就是 make 提高编译效率的关键。4-7-3 使用变量优化多文件 Makefile前面的 Makefilemyproc: main.o add.o sub.o gcc main.o add.o sub.o -o myproc main.o: main.c gcc -c main.c -o main.o add.o: add.c gcc -c add.c -o add.o sub.o: sub.c gcc -c sub.c -o sub.o .PHONY: clean clean: rm -f myproc main.o add.o sub.o可以用变量优化CCgcc TARGETmyproc OBJSmain.o add.o sub.o $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) main.o: main.c $(CC) -c main.c -o main.o add.o: add.c $(CC) -c add.c -o add.o sub.o: sub.c $(CC) -c sub.c -o sub.o .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这样一来如果目标文件名改了只需要修改TARGETmyproc如果 .o 文件列表变了只需要修改OBJSmain.o add.o sub.o4-7-4 使用自动化变量继续优化上面的写法还可以继续优化。原来$(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET)可以写成$(TARGET): $(OBJS) $(CC) $^ -o $因为$^ 表示所有依赖文件也就是 $(OBJS)$ 表示目标文件也就是 $(TARGET)原来main.o: main.c $(CC) -c main.c -o main.o可以写成main.o: main.c $(CC) -c $ -o $因为$ 表示第一个依赖文件也就是 main.c$ 表示目标文件也就是 main.o完整 MakefileCCgcc TARGETmyproc OBJSmain.o add.o sub.o $(TARGET): $(OBJS) $(CC) $^ -o $ main.o: main.c $(CC) -c $ -o $ add.o: add.c $(CC) -c $ -o $ sub.o: sub.c $(CC) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这样已经比最初的写法简洁很多了。4-7-5 模式规则虽然上面的写法已经不错但还有重复main.o: main.c $(CC) -c $ -o $ add.o: add.c $(CC) -c $ -o $ sub.o: sub.c $(CC) -c $ -o $这些规则的结构完全一样.c 文件生成对应的 .o 文件。所以可以使用模式规则。写法如下%.o: %.c $(CC) -c $ -o $这里的 % 可以理解成通配符。它表示相同的文件名前缀。例如%.o: %.c可以匹配main.o: main.c add.o: add.c sub.o: sub.c也就是说main.o 由 main.c 生成add.o 由 add.c 生成sub.o 由 sub.c 生成于是 Makefile 可以写成CCgcc TARGETmyproc OBJSmain.o add.o sub.o $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这就是一个比较常见的 Makefile 写法。4-8 Makefile 的最终推荐模板前面我们一步一步从最简单的 Makefile讲到了变量、自动化变量、多文件编译和模式规则。最后我们整理出一个比较实用的模板。4-8-1 C 语言项目 Makefile 模板CCgcc CFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.c) OBJS$(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这个模板中CCgcc表示使用 gcc 编译器。CFLAGS-Wall -g表示编译选项。其中-Wall 表示开启常见警告-g 表示生成调试信息方便 gdb 调试TARGETmyproc表示最终生成的可执行程序叫 myproc。SRCS$(wildcard *.c)表示获取当前目录下所有 .c 文件。例如当前目录下有main.c add.c sub.c那么$(wildcard *.c)展开后就是main.c add.c sub.cOBJS$(SRCS:.c.o)表示把 SRCS 中所有 .c 后缀替换成 .o 后缀。也就是说main.c add.c sub.c会变成main.o add.o sub.o$(TARGET): $(OBJS) $(CC) $^ -o $表示最终目标 myproc 依赖所有 .o 文件。链接时$^ 表示所有依赖文件也就是所有 .o$ 表示目标文件也就是 myproc%.o: %.c $(CC) $(CFLAGS) -c $ -o $表示所有 .o 文件都可以由同名 .c 文件编译得到。其中$ 表示第一个依赖文件也就是对应的 .c$ 表示目标文件也就是对应的 .o.PHONY: clean clean: rm -f $(TARGET) $(OBJS)表示清理生成的可执行程序和 .o 文件。使用时只需要执行make即可编译项目。执行make clean即可清理项目。以后新增一个 .c 文件时不需要修改 Makefile。比如新增mul.c再次执行make它会自动被编译进去。4-8-2 C 项目 Makefile 模板如果是 C 项目可以改成CXXg CXXFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.cc) OBJS$(SRCS:.cc.o) $(TARGET): $(OBJS) $(CXX) $^ -o $ %.o: %.cc $(CXX) $(CXXFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)如果你的 C 源文件后缀是 .cpp就写成CXXg CXXFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.cpp) OBJS$(SRCS:.cpp.o) $(TARGET): $(OBJS) $(CXX) $^ -o $ %.o: %.cpp $(CXX) $(CXXFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)这里和 C 语言模板的区别主要是C 语言一般使用 gccC 一般使用 gC 编译选项习惯写成 CFLAGSC 编译选项习惯写成 CXXFLAGS4-8-3 一个完整的使用过程假设当前目录下有这些文件main.c add.c sub.c MakefileMakefile 内容如下CCgcc CFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.c) OBJS$(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)第一次执行makemake 会做这些事情1. 找到默认目标 myproc 2. 发现 myproc 依赖 main.o add.o sub.o 3. 发现 main.o add.o sub.o 不存在 4. 根据 %.o: %.c 规则分别编译 main.c add.c sub.c 5. 生成 main.o add.o sub.o 6. 最后链接这几个 .o 文件生成 myproc然后可以运行./myproc如果只修改了 add.c再次执行makemake 会做这些事情1. 发现 add.c 比 add.o 新 2. 所以重新编译 add.c生成新的 add.o 3. 发现 add.o 比 myproc 新 4. 所以重新链接生成新的 myproc 5. main.c 和 sub.c 没有修改所以 main.o 和 sub.o 不会重新生成如果想清理make clean它会执行rm -f myproc main.o add.o sub.o4-8-4 总结Makefile 的核心并不复杂重点记住三件事。第一基本格式目标: 依赖 命令第二make 会根据依赖关系 文件时间戳判断是否需要重新执行命令。第三常用自动化变量$ 表示目标文件 $^ 表示所有依赖文件 $ 表示第一个依赖文件初学时可以先掌握这个模板CCgcc CFLAGS-Wall -g TARGETmyproc SRCS$(wildcard *.c) OBJS$(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $^ -o $ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)以后写小型 C 项目时基本就可以直接套用。只需要修改TARGETmyproc就能生成自己想要的可执行程序名BINproc.exe # 定义变量 CCgcc #SRC$(shell ls *.c) # 采用shell命令行方式获取当前所有.c文件名 SRC$(wildcard *.c) # 或者使用 wildcard 函数获取当前所有.c文件名 OBJ$(SRC:.c.o) # 将SRC的所有同名.c 替换 成为.o 形成目标文件列表 LFLAGS-o # 链接选项 FLAGS-c # 编译选项 RMrm -f # 引入命令 $(BIN):$(OBJ) $(CC) $(LFLAGS) $ $^ # $:代表目标文件名。 $^: 代表依赖文件列表 echo linking ... $^ to $ %.o:%.c # %.c 展开当前目录下所有的.c。 %.o: 同时展开同名.o $(CC) $(FLAGS) $ # %: 对展开的依赖.c文件一个一个的交给gcc。 echo compling ... $ to $ # 不回显命令 .PHONY:clean clean: $(RM) $(OBJ) $(BIN) # $(RM): 替换用变量内容替换它 .PHONY:test test: echo $(SRC) echo $(OBJ)