GCC常用规则和Makefile通用规则解析

GCC常用规则和Makefile通用规则解析

Makefile在编译场景中经常会用到,以前对Makefile是一知半解,只是知道怎么添加文件,怎样编译指定目标,内部是具体怎样运作的,基本上不了解,这篇文章来阐述一下Makefile的基本规则,主要是linux编译下面的Makefile规则。本文会先从GCC的简单使用过渡到借助Makefile进行直接make。的如过有有问题的地方,欢迎在评论区留言一起讨论。

GCC编译的基础知识

一个或者多个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件。在linux编译工程中我们一般使用的是GCC交叉编译工具而不是直接使用GCC,交叉编译工具的安装也很简单,直接使用export导出编译器所在的路径就行了,不建议直接更新在~/bashrc

1、GCC常用编译命令

arm-linux-gcc -o test test.c
命令会编译test.c文件,生成名称为test的可执行文件

该命令简单,不用添加各种各样的其他选项,甚至连-o选项也可以不需要。文件数量少的时候可以直接使用,但是文件多了的时候就不方便了。

gcc -Wp,-MD,abc.dep -c -o main.o main.c
对main.o进行预处理、编译、汇编,但是不链接
同时生成一个main.c里面包含的所有的头文件的列表文件,命名为abc.dep

-Wp,-MD,abc.dep这个选项一般是在Makefile第一次编译的时候用来生成每个文件的头文件依赖列表,方便跟踪后面头文件有变化的时候重新编译涉及到的C文件。

2、GCC常用选项

选项解释
-c把预处理、编译、汇编都做了,但是不链接
-o指定输出文件名称
-I指定头文件目录
-L指定链接时库文件目录
-l指定链接哪一个库文件,-llibrary就是链接名为library的库文件
-D指定宏定义
-Wall打开警告选项
-g打开调试选项,生成可用于GDB调试的信息
-O0 -O1 -O2 -O3优化选项
-static静态编译,在支持动态链接(dynamic linking)的系统上,阻止链接共享库
-Wp,-MD,abc.dep生成依赖的头文件,命令为abc.dep

3、-Wp,-MD,abc.dep的二次说明

下面是一个简单的代码

#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Main fun!\n");
    return 0;
}

使用GCC进行编译gcc -Wp,-MD,abc.dep -o main main.c,编译完成后会在当前目录生成main可执行文件,以及abc.dep头文件依赖列表文件,使用cat adb.dep来查看一下文件内容

main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h
/usr/include/x86_64-linux-gnu/bits/libc-header-start.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/bits/long-double.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/7/include/stddef.h
/usr/include/x86_64-linux-gnu/bits/types.h
/usr/include/x86_64-linux-gnu/bits/typesizes.h
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h
/usr/include/x86_64-linux-gnu/bits/types/FILE.h
/usr/include/x86_64-linux-gnu/bits/libio.h
/usr/include/x86_64-linux-gnu/bits/_G_config.h
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
/usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h

GCC的基础知识到这里就可以了,如果你感兴趣的话,可以去百度GUN在线手册去了解,或者百度《GNU Make 使用手册》进行了解。

Makefile的相关知识

如果你去百度通用Makefile,会出现很多各种各样的Makefile程序,他们似乎大同小异,但是很多都是没有注释,看的让人一脸懵,有写地方都看不懂。Makefile的相关知识也可以参考《GNU Make 使用手册》进行了解。

Makefile的样式

目标(target)…: 依赖(prerequiries)…
<tab>命令(command)

命令被执行的2个条件:

  • 依赖文件比目标文件
  • 目标文件还没生成。

Makefile的常用函数

1、foreach

$(foreach var,list,text)
简单地说,就是对list中的每一个元素,取出来赋给var,然后把var改为text所描述的形式。

objs := a.o b.o
dep_files := $(foreach  f,  $(objs),  .$(f).d)
// 最终 dep_files := .a.o.d .b.o.d
2、wildcard

$(wildcard pattern)
pattern所列出的文件是否存在,把存在的文件都列出来。

src_files := $( wildcard  *.c)
// 最终 src_files中列出了当前目录下的所有.c文件
3、filter

$(filter pattern…,text)
把text中符合pattern格式的内容,filter(过滤)出来、留下来。

obj-y := a.o b.o c/ d/
DIR :=  $(filter  %/,  $(obj-y))
//结果为:c/ d/
4、filter-out

$(filter-out pattern…,text)
把text中符合pattern格式的内容,filter-out(过滤)出来、扔掉。

obj-y := a.o b.o c/ d/
DIR :=  $(filter-out  %/,  $(obj-y))
//结果为:a.o  b.o
5、patsubst

$(patsubst pattern,replacement,text)
寻找text’中符合格式pattern’的字,用replacement’替换它们。pattern’和`replacement’中可以使用通配符。

subdir-y :=  c/  d/
subdir-y := $(patsubst  %/,  %,  $(subdir-y))
// 结果为:c  d
6、shell命令
TOPDIR := $(shell pwd)
这是个立即变量,TOPDIR等于shell命令pwd的结果。
rm -f $(shell find -name "*.o")
// 该命令会递归删除该目录下面所有的.o文件
7、指定Makefile文件命令

我们可以使用“-f”选项指定文件,不再使用名为“Makefile”的文件

make -f Makefile.build

我们可以使用“-C”选项指定目录,切换到其他目录里去

make -C a/ -f Makefile.build
// 用a目录下面的Makefile.build进行编译

我们可以指定目标,不再默认生成第一个目标:

make -C a/ -f Makefile.build other_target

Makefile中自动化变量

  • $@ 目标
  • $< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的
  • $^ 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

Makefile中变量的导出

变量使用export进行导出,导出了之后其他Makefile都可以获取到这个变量。

Makefile中的假想目标

我们的Makefile中有这样的目标:

clean:
rm -f $(shell find -name “*.o”)
rm -f $(TARGET)

如果当前目录下恰好有名为“clean”的文件,那么执行“make clean”时它就不会执行那些删除命令。
这时我们需要把“clean”这个目标,设置为“假想目标”,这样可以确保执行“make clean”时那些删除命令肯定可以得到执行。
使用下面的语句把“clean”设置为假想目标:

.PHONY : clean

Makefile变量的含义

A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果D在前面是延时变量,那么现在它还是延时变量
// 如果D在前面是立即变量,那么现在它还是立即变量

A  =  $@
test:
	@echo $A #使用到的时候$@(目标)是test
变量A的值在执行时才确定,它等于test,是延时变量
A := $@
test:
	@echo $A
变量A的值在立即确定,它等于空,是立即变量

变量的含义似乎用处不大,或者说我还没找到实际的用处,欢迎大家进行指正说明。

顶层Makefile

CROSS_COMPILE =  #这里需要填入你用的gcc交叉编译工具,例如gcc-linux-arm-
AS = $(CROSS_COMPILE)as #指定汇编工具
LD = $(CROSS_COMPILE)ld #指定链接工具
CC = $(CROSS_COMPILE)gcc #指定gcc编译工具
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm

STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

export AS LD CC CPP AR NM #导出一些变量
export STRIP OBJCOPY OBJDUMP

CFLAGS := -Wall -O2 -g # -Wall 打开警告信息 -O2 2级优化 -g 产生调试信息,可以不要
CFLAGS += -I $(shell pwd)/include #指定头文件目录

LDFLAGS :=  #链接标记,可以填入你需要的链接标记

export CFLAGS LDFLAGS #导出

#获取顶部目录,并且导出
TOPDIR := $(shell pwd)
export TOPDIR

TARGET := test #目标名字,以后编译生成的可执行文件的名字就是test


obj-y += main.o #这里指定当前文件需要编译的C文件
obj-y += sub.o
obj-y += a/     #这里指定当前文件夹下面需要递归编译的子文件夹


all : start_recursive_build $(TARGET) #第一个目标,直接执行make的默认目标,它有两个依赖
	@echo $(TARGET) has been built!

start_recursive_build: #第一个依赖,生成的命令是指定makefile进行编译
	make -C ./ -f $(TOPDIR)/Makefile.build 

$(TARGET) : built-in.o #第二个依赖是把当前文件夹下面的built-in.o,链接成为一个可执行文件
	$(CC) -o $(TARGET) built-in.o $(LDFLAGS)

clean: #清空,假想目标,无条件执行
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)

distclean: #删除目录下面所有.o文件,假想目标
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

.PHONY: clean
.PHONY: distclean

顶层Makefile的一个作用是导出各种变量,第二个作用是设定了一个目标all,它有两个依赖,第一个依赖start_recursive_build就是去执行make文件Makefile.build,第二个依赖把当前目录的build-in.o链接生成test这个可执行文件。

Makefile.build文件解析

PHONY := __build #定义一个PHONY变量,立即变量
__build: #设定一个目标,使其成为Makefile.build的第一个目标,注意,最后一行把这个目标设定为伪目标

obj-y :=          #立即变量,清空
subdir-y :=       #立即变量,清空
EXTRA_CFLAGS :=

include Makefile  #包含了当前目录的Makefile,在Makefile中我们定义了obj-y += xxx(main.o compress/  ...... ),以及EXTRA_CFLAGS等变量,这里会全部导入进来

# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))  #筛选出当前目录的目标变量中的子目录,并且去掉/,在这里我们获得了子目录的名字
subdir-y	+= $(__subdir-y)  #获取所有文件子目录名称,赋值给subdir-y

# 获取到所有当前子目录的build-in.o c/built-in.o d/built-in.o #built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) #对于subdir-y里面的每一个值(目录),增加一个相应的目录/built-in.o的变量值

# a.o b.o
cur_objs := $(filter-out %/, $(obj-y)) #获取当前目录下面的所有的a.o b.o之类的文件,去掉文件夹
dep_files := $(foreach f,$(cur_objs),.$(f).d) #获取当前文件的头文件依赖,
dep_files := $(wildcard $(dep_files)) #挑选真正存在的依赖文件,在第一次编译的时候才会将这些文件生成,
# 第一次编译的时候,dep_files变量肯定是空的,因为根本没有.main.o.d之类的文件存在,但是第一次编译之后,就会通过-Wp,-MD,$(dep_file)生成这些.main.o.d类型的文件了
# 第一次编译肯定会编译到所有的头文件
# 第二次以及以后的编译,就有.main.o.d之类的文件存在了,他们会作为依赖,修改了头文件,涉及到的C文件会被重新编译了

ifneq ($(dep_files),)
  include $(dep_files) #导入真正存在的依赖文件,第一次编译该变量是空的,啥也导入不进来
endif


PHONY += $(subdir-y) #子目录文件夹名称也作为一个依赖


__build : $(subdir-y) built-in.o  # 第一个规则,有两个依赖,一个$(subdir-y)子目录名称,另一个是当前目录的build-in.o

$(subdir-y): #子目录文件名依赖,就再去子目录里面编译一下,生成build-in.o文件。如果没有子目录的话,这个命令就不执行了。
	make -C $@ -f $(TOPDIR)/Makefile.build  # 依次进入该子目录变量里面存储的值,使用的Makefile.build进行编译,这里是应用了递归的思想了

built-in.o : $(cur_objs) $(subdir_objs) # 当前目录的build-in.o的生成规则。第一个依赖是当前目录下面的所有.o文件,第二个规则是所有子目录下面的build-in.o文件
	$(LD) -r -o $@ $^ # 该规则的命令:将该目录下的.o和$(subdir_obj)打包(链接)成built-in.o文件

dep_file = .$@.d #第一次编译会生成这个隐藏文件,这个是.c文件的头文件依赖列表

%.o : %.c #当前目录下面的.o文件的生成规则
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
# EXTRA_CFLAGS是对当前目录下面所有的.c文件指定的额外的编译选项,在当前层的Makefile中指定
# CFLAGS_$@是对当前目录下面的指定的.c文件指定的额外的的编译选项,在当前层的Makefile中指定,形式如CFLAGS_main.o := -D DEBUG_SUB3
# -c 把预处理、编译、汇编都做了,但是不链接

.PHONY : $(PHONY) # 将PHONY声明为伪目标,无条件执行

Makefile.build就是一个在整个工程下面的通用Makefile文件,每一级目录的编译都会调用这个文件来进行编译,每一级的目录下面的Makefile功能只是提供obj-y以及额外的编译选项,他会被Makefile.build导入进去。Makefile.build使用的编译方法是递归调用,他会把子目录生成的build-in.o以及当前目录的所有.o文件全部连接成文当前目录的build-in.o,最后一路链接到顶层的build-in.o之后,就可以链接成为一个可执行文件。

我上传了demo工程,可以使用demo工程进行进一步加深印象。

参考链接:韦东山Makefile系列知识教程

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值