①项目代码编译管理(分模块管理)
②节省编译项目时间(更新的需要重新编译)
③一次编写终身受益
(1)、三要素:
目标、依赖、命令,格式如下:
目标:依赖(条件)
命令
/*注意:命令前有一个Tab键。*/
(2)、基本实现:
以加减乘除计算的四个函数为例:
all:add.c sub.c dive.c mul.c main.c
gcc add.c sub.c dive.c mul.c main.c -o app
解析:为了达成all目标(目标名自定义,一般以目标生成文件名为目标名),依赖于一系列“.c”文件。如何达成目标?就是执行命令“gcc add.c sub.c dive.c mul.c main.c -o app”。
(3)、工程编译中的细化:
app:add.o sub.o dive.o mul.o main.o
gcc add.o sub.o dive.o mul.o main.o -o app
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
dive.o:dive.c
gcc -c dive.c
mul.o:mul.c
gcc -c mul.c
main.o:main.c
gcc -c main.c
Makefile工作步骤:
建立关系树(树根节点为需要生成的目标可执行文件)->根据关系树从底到上执行命令->根据依赖文件的最后更改时间是否比目标新,来确定是否需要执行命令进行更新->如果目标不依赖任何文件,则执行对应命令,以示更新。若目标不存在,则认为依赖比目标新。
clean:
-rm -f *.o
-rm -f app
对于上面这个目标指令一般存在于Makefile文件最后,make clean(只执行clean目标的命令,“make + 目标名”只执行目标对应的命令),注意:rm前的“-”表示即使该条命令出错也会继续向后执行。
(4)、make clean的问题:
由于clean无依赖关系所以加上:“.PHONY:clean”用来生成一个伪目标,否则如果Makefile文件所在文件夹中有一个无关的名为clean文件,make clean就会出错,事与愿违。如下:
.PHONY:clean
clean:
-rm -f *.o
-rm -f app
(5)、“@”符号:
不显示命令本身,只显示结果eg:
test:
echo "hello"
@echo "hello"
make test看一看结果:
不加@符号:
加@符号:
(6)、更高级的Makefile(#表示注释):
#$表示索引
#三个重要的变量:"$@"表示目标,"$^"表示所有依赖,"$<"表示依赖中的第一个
obj=add.o sub.o dive.o mul.o main.o
app:$(obj)
gcc $(obj) -o app
#采用内建语法规则去编译(有的Makefile下面默认没有,需要自己写)
%.o:%.c
gcc -c $< -o $@
.PHONY:clean
clean:
-rm -f *.o
-rm -f app
(7)、借助Makefile的函数去优化:
#wildcard函数找到当前目录下的所有.c文件,赋给变量src
src = $(wildcard *.c)
#patsubst函数将匹配到的所有.c转换成.o赋给变量obj
obj = $(patsubst %.c,%.o,$(src))
#为目标文件重命名
target = app
$(target):$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
.PHONY:clean
clean:
-rm -f *.o
-rm -f app
(8)、借助变量去优化(变量名固定):
#指定头文件位置在./include目录中
CPPFLAGS= -I ./include
#指定编译参数
CFLAGS= -g -Wall
#指定链接库位置path与名字,无连接库不赋值即可
#LDFLAGS=
LDFLAGS= -L path -lmycalc
#指定编译器
CC=gcc
#用于Linux嵌入式编译的编译器
#CC=arm-linux-gcc
src = $(wildcard *.c)
obj = $(patsubst %.c,%.o,$(src))
target = app
$(target):$(obj)
$(CC) $^ $(LDFLAGS) -o $@
%.o:%.c
$(CC) -c $< $(CFLAGS) $(CPPFLAGS) -o $@
.PHONY:clean
clean:
-rm -f *.o
-rm -f app
(9)、思考:
根目录下有一个Makefile,下一级目录home中有一个Makefile,如何在根目录下make实现home目录下的项目编译?
实现:在根目录下的Makefile中写:
#-C参数指定进入的路径,即在根目录下执行make时,先读取根目录下的Makefile文件即执行make -C /home/:先进入/home再读取/home下的Makfile文件。
all:
make -C /home/
此外注意:以上规则仅针对了源文件和目标文件,并没有指定头文件如果发生改变,如何?即使头文件发生变化(主要针对自定义头文件,标准库/系统库的头文件我们默认是不会发生改变的),规则也不会更新执行。为此我们需要为目标文件添加除原文件之外的头文件的依赖,比如:
main.o:main.c stalib.c mylib.c string.c ......
但是这样添加会永无止境,每增加一个自定义头文件或删除一个头文件,都需要修改Makefile,所以gcc有-M(生成依赖关系,并打印到屏幕上,同时阻止编译过程)、-MD、-MF等参数对此问题进行解决,主要使用"-MD"(生成.d依赖文件的同时不阻止编译任务,.d文件名默认和.c文件名除后缀外一致)、"-MMD"(过滤掉标准库和系统库头文件,-MM对于-M也是同样效果)、"-MF"(指定输出.d文件名);其他参数具体的内容参照:Linux Makefile 生成 *.d 依赖文件及 gcc -M -MF -MP 等相关选项说明
简单测试如下:
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ ls
include led.c Makefile start.S
#采用 -MD -MF参数
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ gcc -o led.exe -nostartfiles -e led_light -MD -MF led_md.d -I ./include/soc_s3c2440/ led.c
#采用-MMD -MF参数
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ gcc -o led.exe -nostartfiles -e led_light -MMD -MF led_mmd.d -I ./include/soc_s3c2440/ led.c
#很明显,led_mmd.d比led_md.d要小的多,因为缺少系统库和标准库头文件
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ ll
总用量 30
drwxrwxrwx 1 root root 4096 4月 5 21:13 ./
drwxrwxrwx 1 root root 12288 4月 5 21:03 ../
drwxrwxrwx 1 root root 0 4月 5 21:03 include/
-rwxrwxrwx 1 root root 3348 4月 5 21:05 led.c*
-rwxrwxrwx 1 root root 5856 4月 5 21:13 led.exe*
-rwxrwxrwx 1 root root 1696 4月 5 21:12 led_md.d*
-rwxrwxrwx 1 root root 146 4月 5 21:13 led_mmd.d*
-rwxrwxrwx 1 root root 857 4月 5 21:03 Makefile*
-rwxrwxrwx 1 root root 795 4月 5 21:03 start.S*
#-MD -MF led_md.d
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ cat led_md.d
led.exe: led.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/unistd.h \
/usr/include/x86_64-linux-gnu/bits/posix_opt.h \
/usr/include/x86_64-linux-gnu/bits/environments.h \
/usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h \
/usr/include/stdlib.h /usr/include/x86_64-linux-gnu/bits/waitflags.h \
/usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \
/usr/include/x86_64-linux-gnu/bits/endian.h \
/usr/include/x86_64-linux-gnu/bits/byteswap.h \
/usr/include/x86_64-linux-gnu/bits/byteswap-16.h \
/usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \
/usr/include/x86_64-linux-gnu/sys/select.h \
/usr/include/x86_64-linux-gnu/bits/select.h \
/usr/include/x86_64-linux-gnu/bits/sigset.h \
/usr/include/x86_64-linux-gnu/bits/time.h \
/usr/include/x86_64-linux-gnu/sys/sysmacros.h \
/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
include/soc_s3c2440/soc_s3c2440_reg_operator.h \
include/soc_s3c2440/soc_s3c2440_reg.h \
include/soc_s3c2440/soc_s3c2440_field.h
#-MMD -MF led_mmd.d
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ cat led_mmd.d
led.exe: led.c include/soc_s3c2440/soc_s3c2440_reg_operator.h \
include/soc_s3c2440/soc_s3c2440_reg.h \
include/soc_s3c2440/soc_s3c2440_field.h
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$
测试用例如下所示:
A = a b c.pl aaea af bag ia j kka n.txt b.txt c.jpg
#过滤符合格式的参数"$(filter va1 var2 var3 ...,list)"#
B = $(filter %a,$(A))
#过滤掉不符合格式的参数:"$(filter-out va1 var2 var3 ...,list)"#
C = $(filter-out %a %.txt %.jpg %.pl,$(A))
#对列表中的参数执行指定操作:"$(foreach var,list,operator)"#
D_HEAD = $(foreach f,$(C),$(f).h $(f).hpp)
D_SOURCE = $(foreach f,$(C),$(f).c $(f).cpp $(f).i $(f).s $(f).asm $(f).hpp)
D_TARGET = $(foreach f,$(C),$(f).bin $(f).o $(f).obj $(f).exe $(f).pck $(f).sta)
D += $(D_HEAD)
D += $(D_SOURCE)
D += $(D_TARGET)
#获取指定文件下的指定类型文件:"$(wildcard file1 file2 file3...)"#
SRC = $(wildcard *.c *.cpp *.s)
HEAD = $(wildcard *.h *.hpp)
#对指定类型文件名进行修改:"$(patsubst format1 format2 ...,format_trget,list)"#
C_OBJ = $(patsubst %.c,%.o,$(SRC))
CPP_OBJ = $(patsubst %.cpp,%.o,$(SRC))
S_OBJ = $(patsubst %.s %asm,%.o,$(SRC))
first:target_B target_C target_file format_file
@echo A = $(A)
.PHONY:target_B
target_B:
@echo B = $(B)
.PHONY:target_C
target_C:
@echo C = $(C)
.PHONY:target_file
target_file:
@echo D = $(D)
@touch $(D)
.PHONY:target_choose
target_choose:
@echo $1 $2 $3
@touch ${$3}
.PHONY:format_file
format_file:
@echo SRC = $(SRC)
@echo HEAD = $(HEAD)
@echo C_OBJ = $(C_OBJ)
@echo CPP_OBJ = $(CPP_OBJ)
@echo S_OBJ = $(S_OBJ)
.PHONY:clean
clean:
rm -f *.*
.PHONY:clean_head
clean_head:
rm -f *.h *.hpp
.PHONY:clean_source
clean_source:
rm -f *.c *.cpp *.s *.asm *.i
.PHONY:clean_target
clean_target:
rm -f *.o *.bin *.exe *.sta *.pck *.obj
eg:gcc -o test1 test.c(×)
gcc -o test2 -g test.c(√)
我们发现加了-g的链接文件比不加-g的大一些。因为加-g的包含调试信息:
(括号中的字母均是简写)
help(h):查看常用命令类,“help + 类名”查看该类的具体包含的命令;
quit(q):退出;
run(r):全速运行;
start:启动程序,单步执行;
list:查看程序代码,一次显示一段,连续list则按顺序一段一段显示(回车可重复上次命令),“list + 函数名”可查看指定函数;
next(n):逐过程,执行下一步(回车可重复上次命令),不进入语句调用的函数中
step(s):逐语句,执行下一步(回车可重复上次命令),会进入语句调用的函数中
print(p):加变量名可以打印变量内容(eg:print i、print &i等);
break(b):设置断点,"break + 行号"为文件中某行设置断点
continue(c):继续全速运行
info(i):查看GDB内部局部变量的值
info breakpoints:查看设置的断点信息
delete(d) breakpoints 编号:删除指定编号断点
backtrace(bt):显示当前的所在位置的函数调用关系
frame:切换栈帧(frame + 行号),暂时切换到指定行(eg:当前在100行,要查看其他函数的局部变量(假设在第十行):frame 10,在第十行的函数中查看局部变量的值,查看完之后next回到101行,next能回到101行是因为frame(f):是切换栈帧,就是这个意思)
finsh:结束当前函数,返回函数调用点
set:设置变量值(eg:set var n=100,set var buf[2]='x',...)
run argv[1] argv[2]...:调试时命令行传参
display:设置观察点(eg:display num)
undisplay:取消观察点设置(undisplay 观察点编号)
enable breakpoints:启用所有断点
disable breakpoints:禁用所有断点
x:查看内存(eg:x/20x buf,20个字节,按十六进制显示)
watch:被设置观察点的变量被修改时,会打印显示(与display区别)
(info)i watch:显示观察点
core:核心(日志文件)
ulimit -a:查看core文件大小,默认是0
ulimit -c 1024:将core文件大小调节成指定的1024
gdb ./a.out core:便可查看程序bug出现时的信息。
set follow-fork-mode child:跟踪子进程
set follow-fork-mode parent:跟踪父进程