此部分总结自《跟我一起写makefile》
一个代码工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中。
makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
makefile 带来的好处就是——“自动化编译”,一旦写好,
只需要一个 make 命令,整个工 程完全自动编译,极大的提高了软件开发的效率。
make 是一个命令工具,是一个解释 makefile 中指令的命令工具。
Visual C++的 nmake,
Linux 下 GNU 的 make
make 命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所 需要的文件和链接目标程序。
target ... : prerequisites ...
command
...
...
target 也就是一个目标文件,可以是 Object File,也可以是执行文件。
prerequisites 就是,要生成那个 target 所需要的文件或是目标。
command 也就是 make 需要执行的命令。
这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。
prerequisites 中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
这就是Makefile的规则。也就是Makefile中最核心的内容。
######makefile for XXX/src/lib##########
CC=gcc
AR=ar
MODE=RELEASE //默认release版本
LIBRARY= libmsg.a //目标
SOURCE=${wildcard *.c} //扩展通配符,取当前目录下所有.c文件
LIB_OBJS =${patsubst %.c, %.o, $(SOURCE)}
//替换, patsubst 把$(SOURCE)中的变量符合后缀是.c的全部替换成.o
ifeq ($(MODE), DEBUG) //如果是debug版本
CFLAGS = -g3 -ggdb -gdwarf-2 -fstack-protector-all -fno-omit-frame-pointer //编译选项参数
endif
CFLAGS += -W -ggdb -gdwarf-2 -Wall -Wno-unused-parameter -fexceptions
CFLAGS += -Isrc/lib
COMPILE = $(CC) $(CFLAGS) -fPIC -c $^ -o $@
//编译工具gcc 选项参数 -c 编译成目标文件 ($^)表示依赖列表(就是.c)
-o 目标文件名称 ($@)目标(就是.o)
//-fPIC 告诉编译器产生与位置无关代码,全部使用相对地址
all: $(LIBRARY) //编译结果 libmsg.a
$(LIBRARY): $(LIB_OBJS) //编译结果 libmsg.a 依赖 LIB_OBJS 就是 .o 目标文件
rm -f $@ //操作: 删除之前的目标文件(libmsg.a)
$(AR) -crs $@ $^ //打包成 lib ,参数: -crs 创建一个库并写入文件 ($@)目标就是(libmsg.a) ($^)依赖项(.o文件)
%.o: $.c //目标 依赖项
$(COMPILE) //操作:gcc 编译
.PHONY: clean // 这句没有也行, 但是最好加上
clean: //make clean 的处理,删除所有 .o .a .d 等文件
rm -rf *.o
rm -rf *.a
rm -rf *.d
rm -f *~
总结:
make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件
在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错
而对于所定义的命令的错误,或是编译不成功,make根本不理
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中
main.o : main.c test.h
优化版:
main.o : test.h //默认 main.c 自动推导了 不用添加
显式规则。显式规则说明了,如何生成一个或多的的目标文件。
隐晦规则。由于我们的make有自动推导的功能
量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串
文件指示。其包括了三个部分,
一个是在一个Makefile中引用另一个Makefile
另一个是指根据某些情况指定Makefile中的有效部分
还有就是定义一个多行的命令。
注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符
每条规则中的命令和操作系统Shell的命令行是一致的。
make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头
伪目标并不是一个"目标(target)", 不像真正的目标那样会生成一个目标文件.
典型的伪目标是 Makefile 中用来清理编译过程中中间文件的 clean 伪目标, 一般格式如下:
.PHONY: clean <-- 这句没有也行, 但是最好加上
clean:
-rm -f *.o
CC 指定编译器
cc 是unix下面用的编译命令;
gcc 是linux下面用的编译命令;
CFLAGS 表示用于 C 编译器的选项,例如: -W -Wall -g
CXXFLAGS 表示用于 C++ 编译器的选项。
编译参数
-Wall :显示警告讯息
-g GDB能够读取
-c 编译但不进行链接
-lm 数学库
-lpthread 多线程
-ldl 显式加载动态库的动态函数库
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
LIBS 告诉链接器要链接哪些库文件
3.2 LDFLAGS 即链接参数 指定-L虽然能让链接器找到库进行链接
AR=ar ,AR函数库打包程序。默认命令是“ar”。
打包成 lib 如工程中的 libmsg.a
$(AR) -crs $@ $^
c:创建一个库。不管库是否存在,都将创建
s:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引
r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块
linux ar命令
参考:
https://www.runoob.com/linux/linux-comm-ar.html
SOURCE=${wildcard *.c}
wildcard : 扩展通配符 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开
SRC = $(wildcard *.c)
等于指定编译当前目录下所有.c文件,如果还有子目录,比如子目录为inc,则再增加一个wildcard函数,象这样:
SRC = $(wildcard .c) $(wildcard inc/.c)
clean 清空目标文件(.o和执行文件)的规则
notdir : 去除路径
patsubst :替换通配符,patsubst 把$(SOURCE)中的变量符合后缀是.c的全部替换成.o
自动化变量 : @ : 目 标 文 件 , @:目标文件, @:目标文件,^:所有的依赖文件 $<:依赖目标中的第一个目标名字
就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。
这种自动化变量只应出现在规则的命令中。
比如:
edit : main.o kbd.o
$@ 就是edit
$^ 就是main.o kbd.o
$< 依赖目标中的第一个目标名字。
COMPILE 这个命令的一个标示,可以任意起名字
make 参数
-C DIR,–directory=DIR 在读取 Makefile 之前,进入到目录 DIR,然后执行 make。
make -C app //编译app目录下的makefile文件
make clean 清除编译结果
清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件
参考:
make命令参数和选项大汇总
http://c.biancheng.net/view/7126.html
all 第一个目标,功能一般是编译所有的目标
这通常是一个虚拟目标,它不会创build任何文件,而仅仅依赖于其他文件。如: all: $(TARGET)
all目标通常是makefile中的第一个目标,因为如果你只是在命令行写入make ,而不指定目标,它将会build立第一个目标。
模式字符串替换函数:patsubst
$(patsubst ,, )
功能:查找 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)
是否符合模式,如果匹配的话,则以替换
例子:
B_OBJS =${patsubst %.c, %.o, KaTeX parse error: Expected 'EOF', got '}' at position 9: (SOURCE)}̲ //替换, patsub…(SOURCE)中的变量符合后缀是.c的全部替换成.o
当优化标识被启用之后,gcc编译器将会试图改变程序的结构(当然会在保证变换之后的程序与源程序语义等价的前提之下),以满足某些目标,如:代码大小最小或运行速度更快(只不过通常来说,这两个目标是矛盾的,二者不可兼得)。
一般来说,如果不指定优化标识的话,gcc就会产生可调试代码,每条指令之间将是独立的:可以在指令之间设置断点,使用gdb中的 p命令查看变量的值,改变变量的值等。并且把获取最快的编译速度作为它的目标。
O1优化会消耗不少多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。
这两个命令的效果是一样的,目的都是在不影响编译速度的前提下,尽量采用一些优化算法降低代码大小和可执行代码的运行速度。
O2会尝试更多的寄存器级的优化以及指令级的优化,
它会在编译期间占用更多的内存和编译时间。
该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度。
与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。
O3在O2的基础上进行更多的优化,采取很多向量化算法
例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。
该选项除了执行-O2所有的优化选项之外,一般都是采取很多向量化算法,提高代码的并行执行程度,利用现代CPU中的流水线,Cache等。
Os主要是对代码大小的优化,我们基本不用做更多的关心。
打开了大部分O2优化中不会增加程序大小的优化选项,并对程序代码的大小做更深层的优化。(通常我们不需要这种优化)
1. 调试问题
任何级别的优化都将带来代码结构的改变。
例如:对分支的合并和消除,
对公用子表达式的消除,
对循环内load/store操作的替换和更改等,
都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。
2. 内存操作顺序改变所带来的问题
在O2优化后,编译器会对影响内存操作的执行顺序。
MODE=RELEASE
ifeq ($(MODE), DEBUG)
CFLAGS = -g3 -ggdb -gdwarf-2 -fstack-protector-all -fno-omit-frame-pointer
endif
/* release 版本编译选项*/
……
src/obj$ ls -lh
total 168K -rwxr-xr-x 1 root 147K Jul 20 11:35 msg_to_test
src/obj$ readelf -S msg_to_test | grep debug
src/obj$
/src$ make MODE=DEBUG //make 参数 debug版本
src/obj$ ls -lh
total 444K -rwxr-xr-x 1 root 420K Jul 20 11:34 msg_to_test
src/obj$
src/obj$ readelf -S msg_to_test | grep debug
[27] .debug_aranges PROGBITS 0000000000000000 00020763
[28] .debug_info PROGBITS 0000000000000000 000208e3
[29] .debug_abbrev PROGBITS 0000000000000000 00029c65
[30] .debug_line PROGBITS 0000000000000000 0002acfc
[31] .debug_str PROGBITS 0000000000000000 0002ee5e
[32] .debug_loc PROGBITS 0000000000000000 00054236
[33] .debug_ranges PROGBITS 0000000000000000 00057c56
[34] .debug_macro PROGBITS 0000000000000000 00057cb6
src/obj$
src/obj$ readelf -S msg_to_test | grep debug
[27] .debug_aranges PROGBITS 0000000000000000 00020763
[28] .debug_info PROGBITS 0000000000000000 000208e3
[29] .debug_abbrev PROGBITS 0000000000000000 00029c65
[30] .debug_line PROGBITS 0000000000000000 0002acfc
[31] .debug_str PROGBITS 0000000000000000 0002ee5e
[32] .debug_loc PROGBITS 0000000000000000 00054236
[33] .debug_ranges PROGBITS 0000000000000000 00057c56
[34] .debug_macro PROGBITS 0000000000000000 00057cb6
src/obj$
参考:
大全
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=408225
https://blog.csdn.net/weixin_38391755/article/details/80380786/
https://www.cnblogs.com/wang_yb/p/3990952.html