一 Makefile简介
1.1什么是Makefile?
Makefile是一个工程管理的工具。它的本质是一个文件,这个文件中存放的是对代码的编译的规则。Makefile就会根据文件的"时间戳"来决定工程内的文件本次是否需要参与编译。
1.2什么是make?
make是一个可执行程序,在/usr/bin目录下存放着如果在/usr/bin目录下找不到这个程序,需要使用sudo apt-get install make来安装这个程序。当在终端上执行make的时候,它就会解析当前目录下的Makefile文件。并根据Makefile里的编译规则来编译当前的工程。
1.3为什么使用?
在实际的开发过程中,仅仅使用gcc命令对程序进行编译时非常低效的,原因主要有以下两点:
(1)程序往往是由多个源文件组成的,源文件的个数越多,那么gcc的命令越长。此外,各种编译规则也会加大gcc命令行的复杂度,所以在开发调试的过程中,通过输入gcc命令行来编译程序时很麻烦的。
(2)在程序的整个开发过程中,调试的工作量占到了整体工作量的70%以上。在调试程序的过程中,每次调试一般只会修改部分源文件。而在使用gcc命令行编译程序时,gcc会把那些没有被修改的源文件一起编译,这样就会影响编译的总体效率。
为了提高编译程序的效率,很多基于windows平台上的开发工具都提供了工程管理器。用户只需要单击一个“make”按钮就可以启动工程管理器对整个程序进行自动编译,在整个编译的过程中是不需要人工干预的。这种工程管理器被形象的称为全自动工程管理器。
GCC提供了半自动化的工程管理器Make.所谓半自动化是指在使用工程管理器前需要人工编写程序的编译规则,所有的编译规则都保存在Makefile文件中,全自动化的工程管理器在编译程序前会自动生成Makefile文件。
1.4.优越性
(1)使用方便。通过命令”make“就可以启动Make工程管理器对程序进行编译,所以不再需要每次都输入gcc命令行。Make启动后会根据Makefile文件中的编译规则命令自动对源文件进行编译链接,最终生成可执行文件。
(2)调试效率高。为了提高编译程序的效率,Make会检查每个源文件的修改时间(时间戳)。只有在上次编译之后被修改的源文件才会在接下来的编译过程中被编译和链接,这样就能避免多余的编译工作量。为了保证源文件具有正确的时间戳,必须保证操作系统时间的正确性(注意 VMWare虚拟机的CMOS时间是否正确)。
二.makefile
2.1 makfile编译规则
Make工程管理器是完全根据Makefile文件中的编译规则命令进行工作的,Makefile文件由以下三项基本内容组成:
(1) 需要生成的目标文件(Target file)
(2) 生成目标文件所需要的依赖文件(dependency)
(3) 生成目标文件的编译规则命令行(command)
这三项内容按照如下格式进行组织:
target file:dependency file
command
其中,makefile规则在书写command命令前必须加一个键。
Makefile工程管理器在编译程序时会检查每个依赖文件的时间戳,一旦发现某个依赖文件的时间戳比目标文件要新,就会执行目标文件的规则命令来重新生成目标文件。这个过程称为目标文件的依赖规则检查。依赖规则检查时Make工程管理器的最核心的工作任务之一。下面以编译程序test(由 a.c b.c 和b.h组成)为例来描述Make的工作过程。
//a.c #include "b.h" int main() { hello(); return 0; } //b.h void hello(); //b.c #include <stdio.h> void hello() { printf("hello"); }
//makefile test: a.o b.o cc a.o b.o -o test // -o 指定输出文件名 a.o:a.c b.h cc -c a.c //-c 只编译不链接,生成目标文件 b.o:b.c cc -c b.c
Make工程管理器编译Test程序的过程如下:
(1) Make工程管理器首先会在当前目录下读取Makefile文件
(2) 查找Makefile文件中的第一个目标文件(在本例中为test),该文件也是Make工程管理器本次编译任务的最终目标。
(3) 把目标文件test的依赖文件当作目标文件进行依赖规则检查。这是一个递归的检查过程,在本例中就是依次把a.o和b.o作为目标文件来检查各自的依赖规则。Make会根据以下三种情况进行处理。
① 如果当前目录下没有或缺少依赖文件,则执行其规则命令生成依赖文件(假如缺少a.o,则执行命令”cc -c a.c“生成a.o)。
② 如果存在依赖文件,则把其作为目标文件来检查依赖规则(假如 a.c比a.o新,则执行命令”cc -c a.c“更新a.o)
③ 如果目标文件比所有依赖文件都新,则不做处理。
(4) 递归执行第三步之后,就会得到目标文件test所有最新的依赖文件了,接着Make会根据以下三种情况进行处理:
① 如果目标文件test不存在(比如第一次编译),则执行规则命令生成test
② 如果目标文件test存在,但存在比test要新的依赖文件,则执行规则命令更新test。
③ 目标文件test存在,且比所有依赖文件新,则不做处理。
2.2 Makefile特性介绍
源文件数量越是多的程序,其编译规则就会越复杂,导致Makefile文件也就越复杂。为了简化Makefile的编写,丰富编译程序的方法和手段,Makefile提供了很多类似高级编程语言的语法机制。
2.2.1 变量
在Makefile文件中,存在大量的文件名,而且这些文件名都是重复出现的。所以在源文件比较多的情况下,很容易发生遗漏或写错文件名。而且一旦源文件的名称发生了变化,还容易造成与其它文件名不一致的错误。于是,Makefile提供了变量名来代替文件名。变量的使用方式:
$ (变量名) 或 ${变量名}
例如:
obj = a.o b.o test : $(obj) cc -o test $(obj) a.o:a.c b.h cc -c a.c b.o:b.c cc -c b.c
该Makefile中用变量obj来代替”a.o b.o“,当源文件名发生改动或增删源文件时,只要对变量obj的值斤西瓜相应的修改就可以了,这样就可以避免文件名不一致或遗漏的错误。Makefile中的变量的命名可以使用字符,数字和下划线,但要注意变量名对大小写是敏感的。
变量定义的方式:
(一)通过 ”=“来实现。例如:
a1 = $(s2);
a2 = $(s3);
a3 = a.o
这种方式下变量a1的值是a.o,也就是说前面的变量可以通过后面的变量来定义。但使用这种方式定义变量时,要防止出现死循环情况。
(二)通过 ”:=“来实现。例如:
a1:= a.o a2:= $(a1) b.o
这种方式下变量a1的值是a.o,变量a2的值是a.o b.o。例如:
a1:= $(a2) b.o a2:= a.o
这张方式下变量a1的值是b.o,而不是”a.o b.o“,也就是说前面的变量不能通过后面的变量来定义。
(三)通过 ”+=“来实现。例如:
a1 = a.o; a1 += b.o; 这种方式下变量a1的值是”a.o b.o“。也就是说”+=“可以实现给变量追加值。等同于如下示例: a1 = a.o; a1:=$(a1) b.o 可以看出,Makefile的”+=“和c语言中的"+="是十分相似的。
(四)通过 "?="来实现。例如:
a1 := a.o a1 ?= b.o
这种方式下变量a1的值是a.o,而不是b.o,也就是说,如果变量a1已经在前面定义过了,那么后面的定义就无效了。
2.2.2 自动推导
为了进一步简化Makefile书写,Make工程管理器提供了自动推导功能。自动推导功能默认每个目标文件都有一个与之对应的依赖文件。例如,a.o文件有依赖文件a.c与之对应,这样在Makefile中就不需要指定与目标文件对应的依赖文件名了。此外,自动推导功能还能推导出与目标文件对应的基本编译规则命令。例如,a.o文件的规则命令为”gcc -c -o a.c“
例如:
obj = a.o b.o test:$(obj) cc -o test $(obj)
结果为:
linux@linux:~/81-makefile/test$ make cc -c -o a.o a.c cc -c -o b.o b.c cc -o test a.o b.o
可以看到,Makefile分别推导出了目标文件a.o和b.o的规则命令”cc -c -o a.o a.c“与”cc -c -o b.o b.c“。
伪目标:伪目标不是真正的目标文件,只是一个符号。为了不和真是的目标文件混淆,最好使用”.PHONY“对伪目标进行标识。例如:
obj = a.o b.o .PHONY: all all: test $(obj) test:$(obj) cc -o test $(obj) .PHONY:clean clean: rm -rf *.o test test_dir = ~/81-makefile/t_d .PHONY: install install: mkdir $(test_dir) cp test $(test_dir) .PHONY: uninstall uninstall: rm -rf $(test_dir)
(1) all。运行命令”make all“后,Make会把all当成最终的目标。由于伪目标和真实目标一样都有依赖文件,所以Make会更新all的依赖文件test,a.o和b.o。如下所示:
linux@linux:~/81-makefile/test$ make all cc -c -o a.o a.c cc -c -o b.o b.c cc -o test a.o b.o
(2) clean。运行命令”make clean“后,Make会执行命令:rm -rf *.o test,,这样所有的.o文件和test就全被删除了。如下所示:
linux@linux:~/81-makefile/test$ make clean rm -rf *.o test
(3)install。运行命令”make install“后,Make会顺序执行命令”mkdir $(test_dir)“和”cp test $(test_dir)“,把test文件复制到test_dir变量指定的目录中去(这里只是模拟安装过程,并不是真正的安装方法),如下所示:
linux@linux:~/81-makefile/test$ make install mkdir ~/81-makefile/t_d cp test ~/81-makefile/t_d
(4)uninstall 。运行命令”make clean“后,Make会执行命令”rm -rf $(test_dir)“。这样就可以把变量test_dir指定的目录以及目录中的文件全部删除。如下所示。
linux@linux:~/81-makefile/test$ make uninstall rm -rf ~/81-makefile/t_d
在Makefile中,伪目标是非常有用的。例如,在递归编译,并行编译等场合中,使用伪目标可以方便地控制编译过程。
2.3 文件查找
为了便于管理和组织,程序的源文件都根据功能的不同放置在不同的子目录中。但源文件被分散存储后,Makefile又如何才能找到这些源文件呢?Makefile提供了以下两种方法。
(1)VPATH = 目录 :目录 …
例如:
export VPATH= /a : /b
Make会在当前的路径找不到文件时按照顺序依次查找/a和/b目录
(2)vpath。和VPATH不同的是,vpath并不是变量,而是关键字,其作用和VPATH类似,但使用方式更加灵活。vpath的使用方法为
vpath 模式 目录:目录…
例如:
vpath %.c /a : /b
Make会在当前路径找不到文件时,按照顺序依次查找/a和/b目录中所有的C文件。vpath也可以对不同路径采用不同的搜索模式。例如:
vpath %.c /a : /b
make会在当前路径找不到文件时按照顺序依次查找/a和/b目录中所有的C文件。vpath也可以对不同的路径采用不同的搜索模式。例如:
vpath %.c /a vpath %.h /b
Make会在当前路径找不到源文件时先查找/a目录下的C文件,然后查找/b目录下的头文件。例如,首先在/home目录下新建一个目录b,然后把b.c文件放入目录b中。
VPATH = /home/b obj = a.o b.o .PHONY : all all:test $(obj) test:$(obj) cc -o test $(obj)
练习:
将多文件编译的Makefile加上变量
TARGET?=demo OBJS:=main.o add.o sub.o CC:=gcc CFLAGS:= -c -o CFLAGSs:=-o $(TARGET):$(OBJS) $(CC) $(OBJS) $(CFLAGSs) $(TARGET) main.o:main.c $(CC) main.c $(CFLAGS) main.o add.o:add.c $(CC) add.c $(CFLAGS) add.o sub.o:sub.c $(CC) sub.c $(CFLAGS) sub.o clean: rm $(OBJS) $(TARGET)
2.4 Makefile中的通配符
Makefile中的通配符:*和% 在Makefile中使用命令,这个*就是命令的通配符 %它是Makefile文件所特有的通配符 #aa=`ls *.c` #命令置换 aa=$(shell ls *.c) #在Makefile中起一个shell,将命令在shell中执行, #将执行的结果赋值给aa变量
2.5Makefile命令中的特殊字符
$@:目标 $<:第一个依赖 $^:所有的依赖 $*:目标去除后缀之后的字段
练习:
1.多文件编译优化版本
TARGET?=demo OBJS:=main.o add.o sub.o CC:=gcc CFLAGS:= -c -o CFLAGSs:=-o $(TARGET):$(OBJS) $(CC) $(OBJS) $(CFLAGSs) $(TARGET) %.o:%.c $(CC) $< $(CFLAGS) $@ clean: rm $(OBJS) $(TARGET)
2.6条件判断
(1)和C语言的条件编译类似,make也可以在运行时进行条件判断,然后进入分支继续编译,条件判断的书写格式为:
条件表达式 如果真执行的文本段 endif
或者
条件表达式 如果真执行的文本段 else 如果假执行的文本段 endif
(2)条件表达式的四种格式
1> ifeq(参数1,参数2)。作用:比较参数1和参数2的值是否相同,相同为真,相异为假
2> ifneq(参数1,参数2)。作用:比较参数1和参数2的值是否相同,相异为真,相同为假
3> ifdef(参数)。作用:参数非空为真,空为假。
4> ifndef(参数)。作用:参数空为真,非空为假。
例如:
a1 = a.o a2 = b.o ifeq ($(a1),$(a2)) a1 = x.o else a2 = y.o
变量a1的值为a.o,变量a2的值为y.o
二、分文件
main.c #include "func.h" int main(int argc, const char *argv[]) { func(); printf("a + b = %d\n",add(100,200)); return 0; }
func.c
#include "func.h" void func() { printf("hello world!\n"); } int add(int x,int y) { return x + y; }
func.h
```c #ifndef _FUNC_H_ #define _FUNC_H_ #include <stdio.h> void func(); int add(int x,int y); #endif
三、gdb
gdb(代码调试的工具)的使用
编译:
gcc -g ***.c -o ***
-g : 添加调试的信息
运行:
gdb ./可执行文件
运行之后:
(gdb) 输入gdb命令 查看文件源代码(list 简写为l) (gdb) l 显示源代码,默认向后显示的10行 (gdb) l num 围绕num显示10行 (gdb) l - 向前显示num行 (gdb) l start,end 显示从start开始到end结束的行 设置断点(break 简写为b) (gdb) b 行号 (gdb) b 函数名 disable + 断点的编号 禁用某一个断点 enable + 断点的编号 使能某一个断点 delete + 断点的编号 删除一个断点 查看断点的设置信息(info) (gdb) info b 运行代码 (run 简写为r) (gdb) r 查看变量的值(print 简写p) (gdb) p i 恢复程序的运行(continue 简写为c) (gdb) c 单步执行 (next 简写为n) (gdb) n 单步运行之前,必须先执行run 查看帮助信息 (help 简写为h) (gdb) h 命令 如果在(gdb)后边什么也不输入,直接回车, 默认执行上一条输入的指令 退出调试状态(quit 简写为q) (gdb) q
四、通讯录
1.通过makefile管理代码
2.各个子功能:
a:添加用户(连续添加)
b:查看用户(按照姓名的首字母排序)
c:搜索用户(两种方式:id,用户名)
d:删除用户信息
e:修改用户信息
3.自己封装字符串处理函数