makefile[一]:编译选项,debug/release版本 + gcc 编译选项

1. makefile 介绍

此部分总结自《跟我一起写makefile》

1.1 什么是 makefile ?

一个代码工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中。
makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。

1.2. makefile 优势

makefile 带来的好处就是——“自动化编译”,一旦写好,
只需要一个 make 命令,整个工 程完全自动编译,极大的提高了软件开发的效率。

1.3. 什么是 make ?

make 是一个命令工具,是一个解释 makefile 中指令的命令工具。
Visual C++的 nmake,
Linux 下 GNU 的 make

make 命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所 需要的文件和链接目标程序。

1.4 makefile 规则

target ... : prerequisites ...             
	command             
	...             
	... 

target 也就是一个目标文件,可以是 Object File,也可以是执行文件。
prerequisites 就是,要生成那个 target 所需要的文件或是目标。
command 也就是 make 需要执行的命令。
这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。

1.4.1 makefile 最核心的内容

prerequisites 中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
这就是Makefile的规则。也就是Makefile中最核心的内容。

1.5 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 *~

1.5 make 工作流程

  1. make在当前目录下找名字叫“Makefile”或“makefile”的文件
  2. 如果找到,它会找文件中的第一个目标文件(target),如上面的 all(libmsg.a),并把这个文件作为最终的目标文件
  3. 如果 all 不存在;或者all后的依赖项文件比它新,那么就执行下面的命令生成all这个文件
  4. 如果LIBRARY所依赖的.o 不存在;或者.o文件比较新,那么就去寻找.o的依赖项,找到再根据其生成.o文件……嵌套查找编译生成
  5. 最后一层一层的生成,最终生成目标文件(如上面makefile中的libmsg.a)

总结:
make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件

1.5.1 make错误处理:

在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错
而对于所定义的命令的错误,或是编译不成功,make根本不理

1.5.2 make自动推导

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中

main.o : main.c test.h
优化版:
main.o : test.h  //默认 main.c 自动推导了 不用添加

1.6 makefile的五个东西:显示规则,隐晦规则,变量定义,文件指示和注释

显式规则。显式规则说明了,如何生成一个或多的的目标文件。
隐晦规则。由于我们的make有自动推导的功能
量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串
文件指示。其包括了三个部分,
一个是在一个Makefile中引用另一个Makefile
另一个是指根据某些情况指定Makefile中的有效部分
还有就是定义一个多行的命令。
注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符

1.7 Makefile 书写命令

每条规则中的命令和操作系统Shell的命令行是一致的。
make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头

1.8 伪目标

伪目标并不是一个"目标(target)", 不像真正的目标那样会生成一个目标文件.
典型的伪目标是 Makefile 中用来清理编译过程中中间文件的 clean 伪目标, 一般格式如下:

.PHONY: clean   <-- 这句没有也行, 但是最好加上
clean:
	-rm -f *.o

2. 编译选项说明

2.1 编译选项说明

  1. CC 指定编译器
    cc 是unix下面用的编译命令;
    gcc 是linux下面用的编译命令;

  2. CFLAGS 表示用于 C 编译器的选项,例如: -W -Wall -g
    CXXFLAGS 表示用于 C++ 编译器的选项。

  3. 编译参数
    -Wall :显示警告讯息
    -g GDB能够读取
    -c 编译但不进行链接
    -lm 数学库
    -lpthread 多线程
    -ldl 显式加载动态库的动态函数库
    -fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

  4. LIBS 告诉链接器要链接哪些库文件
    3.2 LDFLAGS 即链接参数 指定-L虽然能让链接器找到库进行链接

  5. AR=ar ,AR函数库打包程序。默认命令是“ar”。
    打包成 lib 如工程中的 libmsg.a
    $(AR) -crs $@ $^
    c:创建一个库。不管库是否存在,都将创建
    s:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引
    r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块
    linux ar命令

参考:
https://www.runoob.com/linux/linux-comm-ar.html

  1. SOURCE=${wildcard *.c}
    wildcard : 扩展通配符 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开
    SRC = $(wildcard *.c)
    等于指定编译当前目录下所有.c文件,如果还有子目录,比如子目录为inc,则再增加一个wildcard函数,象这样:
    SRC = $(wildcard .c) $(wildcard inc/.c)

  2. clean 清空目标文件(.o和执行文件)的规则

  3. notdir : 去除路径
    patsubst :替换通配符,patsubst 把$(SOURCE)中的变量符合后缀是.c的全部替换成.o

  4. 自动化变量 : @ : 目 标 文 件 , @:目标文件, @:^:所有的依赖文件 $<:依赖目标中的第一个目标名字
    就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。
    这种自动化变量只应出现在规则的命令中。

    比如:
    edit : main.o kbd.o
    $@ 就是edit
    $^ 就是main.o kbd.o
    $< 依赖目标中的第一个目标名字。

  5. COMPILE 这个命令的一个标示,可以任意起名字

  6. 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

  1. all 第一个目标,功能一般是编译所有的目标
    这通常是一个虚拟目标,它不会创build任何文件,而仅仅依赖于其他文件。如: all: $(TARGET)
    all目标通常是makefile中的第一个目标,因为如果你只是在命令行写入make ,而不指定目标,它将会build立第一个目标。

  2. 模式字符串替换函数:patsubst

    $(patsubst ,, )
    功能:查找 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)
    是否符合模式,如果匹配的话,则以替换

    例子:
    B_OBJS =${patsubst %.c, %.o, KaTeX parse error: Expected 'EOF', got '}' at position 9: (SOURCE)}̲ //替换, patsub…(SOURCE)中的变量符合后缀是.c的全部替换成.o

2.2 gdb 没有 行数 解决: 添加 -g3 -ggdb -gdwarf-2

2.3 GCC中 -O0 -O1 -O2 -O3 -Os 优化介绍

当优化标识被启用之后,gcc编译器将会试图改变程序的结构(当然会在保证变换之后的程序与源程序语义等价的前提之下),以满足某些目标,如:代码大小最小或运行速度更快(只不过通常来说,这两个目标是矛盾的,二者不可兼得)。

2.3.1 -O0: 不做任何优化,这是默认的编译选项。

一般来说,如果不指定优化标识的话,gcc就会产生可调试代码,每条指令之间将是独立的:可以在指令之间设置断点,使用gdb中的 p命令查看变量的值,改变变量的值等。并且把获取最快的编译速度作为它的目标。

2.3.2 -O,-O1: 对程序做部分编译优化

O1优化会消耗不少多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。 

这两个命令的效果是一样的,目的都是在不影响编译速度的前提下,尽量采用一些优化算法降低代码大小和可执行代码的运行速度。

2.3.3 -O2 是比O1更高级的选项,进行更多的优化。

O2会尝试更多的寄存器级的优化以及指令级的优化,
	它会在编译期间占用更多的内存和编译时间。 

该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度。

与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。

2.3.4 -O3 采取很多向量化算法,提高代码的并行执行程度

O3在O2的基础上进行更多的优化,采取很多向量化算法
	例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。 

该选项除了执行-O2所有的优化选项之外,一般都是采取很多向量化算法,提高代码的并行执行程度,利用现代CPU中的流水线,Cache等。

2.3.5 -Os: 主要是对程序的尺寸进行优化。

Os主要是对代码大小的优化,我们基本不用做更多的关心。

打开了大部分O2优化中不会增加程序大小的优化选项,并对程序代码的大小做更深层的优化。(通常我们不需要这种优化)

2.3.6 优化代码有可能带来的问题

1. 调试问题
	任何级别的优化都将带来代码结构的改变。
	例如:对分支的合并和消除,
		对公用子表达式的消除,
		对循环内load/store操作的替换和更改等,
	都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。 

2. 内存操作顺序改变所带来的问题
	在O2优化后,编译器会对影响内存操作的执行顺序。

3. debug版本和release版本编译及区分

3.1 makefile debug/release版本编写

MODE=RELEASE

ifeq ($(MODE), DEBUG)
CFLAGS = -g3 -ggdb -gdwarf-2 -fstack-protector-all -fno-omit-frame-pointer
endif

/* release 版本编译选项*/
……

3.2 默认release 版本 - 大小 147K

src/obj$ ls -lh
total 168K	-rwxr-xr-x 1 root vpn 147K Jul 20 11:35 msg_to_test
src/obj$ readelf  -S msg_to_test | grep debug
src/obj$

3.3 编译debug版本 - 大小 420K

/src$ make MODE=DEBUG  //make 参数 debug版本 

src/obj$ ls -lh
total 444K	-rwxr-xr-x 1 root vpn 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$

3.4 查看debug还是release - readelf -S a.out | grep debug

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

  • 2
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我明白了您的问题。以下是针对您的问题的Makefile文件,可以实现您所需的所有功能: ```makefile # 工程名称 PROJECT_NAME := ParseConfig # 编译选项 CC := gcc CFLAGS := -Wall -Werror -std=c99 -I./include # 链接器选项 LD := gcc LDFLAGS := -L./lib # 源文件和目标文件 SRCS := $(wildcard ./src/*.c) OBJS := $(patsubst %.c, %.o, $(SRCS)) RELEASE_OBJS := $(addprefix ./RELEASE/, $(OBJS)) DEBUG_OBJS := $(addprefix ./DEBUG/, $(OBJS)) LIBRARIES := -lParseConfig # 目标文件 TARGETS := ./lib/libParseConfig.a ./lib/libParseConfig.so ./bin/$(PROJECT_NAME) # 默认目标 .PHONY: all all: $(TARGETS) # 生成libParseConfig.a ./lib/libParseConfig.a: $(OBJS) mkdir -p ./lib ar rcs $@ $^ # 生成libParseConfig.so ./lib/libParseConfig.so: $(OBJS) mkdir -p ./lib $(LD) -shared -o $@ $^ # 生成ParseConfig ./bin/$(PROJECT_NAME): ./src/main.o ./lib/libParseConfig.a mkdir -p ./bin $(LD) $(LDFLAGS) -o $@ $^ $(LIBRARIES) # 编译Release版本 .PHONY: release release: CFLAGS += -Os -DNDEBUG release: $(RELEASE_OBJS) ./lib/libParseConfig.so ./bin/$(PROJECT_NAME) strip ./lib/libParseConfig.so strip ./bin/$(PROJECT_NAME) ./RELEASE/%.o: ./src/%.c mkdir -p ./RELEASE $(CC) $(CFLAGS) -c $< -o $@ # 编译Debug版本 .PHONY: debug debug: CFLAGS += -g debug: $(DEBUG_OBJS) ./lib/libParseConfig.so ./bin/$(PROJECT_NAME) ./DEBUG/%.o: ./src/%.c mkdir -p ./DEBUG $(CC) $(CFLAGS) -c $< -o $@ # 生成静态Release版本 .PHONY: static_release static_release: CFLAGS += -Os -DNDEBUG static_release: ./src/main.o ./lib/libParseConfig.a mkdir -p ./bin $(LD) -static -o ./bin/$(PROJECT_NAME) $^ strip ./bin/$(PROJECT_NAME) # 生成静态Debug版本 .PHONY: debug_release debug_release: CFLAGS += -g debug_release: ./src/main.o ./lib/libParseConfig.a mkdir -p ./bin $(LD) -static -o ./bin/$(PROJECT_NAME) $^ strip ./bin/$(PROJECT_NAME) # 清除所有中间文件和目标文件 .PHONY: clean clean: rm -rf ./bin/* ./lib/* ./RELEASE/* ./DEBUG/* ``` 您可以将此Makefile文件保存为名为“Makefile”的文件,并将其放置在您的项目根目录中。在终端中,进入项目根目录并输入“make”即可自动完成编译和链接过程,生成libParseConfig.a,libParseConfig.so和ParseConfig程序。输入“make release”可以编译生成的.o文件存放于RELEASE目录,编译参数包含-Os -DNDEBUG,链接完成之后对libParseConfig.so和ParseConfig自动进行strip。输入“make debug”可以编译生成的.o文件存放于DEBUG目录,编译参数包含-g,链接完成之后libParseConfig.so和ParseConfig不strip。输入“make static_release”可以生成的ParseConfig自动strip,并且不依赖libParseConfig.so。输入“make debug_release”可以ParseConfi不strip,并且不依赖libParseConfig.so。输入“make clean”可以清除所有的二进制文件和中间文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值