韦东山嵌入式教程第四篇Linux基础知识学习笔记(1)——Makefile的使用

韦韦东山嵌入式教程第四篇Linux基础知识学习笔记(1)——Makefile的使用

第4篇:嵌入式Linux应用开发基础知识


文章目录

  • 韦韦东山嵌入式教程第四篇Linux基础知识学习笔记(1)——Makefile的使用
  • 前言
  • 一、Makefile最基本的规则
    • 1.使用makefile的优点是什么呢?
    • 2.规则一
    • 3.规则二
  • 二、makefile的基本语法
    • 1. 通配符
    • 2. 假想目标: .PHONY
    • 3. 变量
      • 1)简单变量:
      • 2)延时变量
  • 三、Makefile函数
    • 1、函数foreach
    • 2、函数filter/filter-out
    • 3、wildcard函数
    • 4、patsubst函数
  • 四、利用gcc自动生成依赖
    • 1、为何要使用.d文件
    • 2、gcc -M -MF -MD 等相关选项的说明
    • 3、该博主推荐的makefile
  • 五、CFLAGS
  • 总结


前言

我们在VS中可以轻而易举地编译并运行代码,其背后的原理即为Makefile,接下来我将详细总结与整理韦老师所讲的Makefile使用技巧。


一、Makefile最基本的规则

1.使用makefile的优点是什么呢?

1、当工程中遇到成百上千的文件同时编译时,如果修改了其中一个源文件,那么当你重新使用gcc -o直接编译生成应用程序时,会耗费大量时间,使用makefile的话,可以自定义编译规则和步骤,如果只有部分文件被修改,那么只用编译部分文件然后再链接即可。
2、能节约敲命令的时间,只用打出make命令就能直接编译链接

2.规则一

目标文件:依赖文件
命令

例一:

test:a.o
	gcc -o test a.o
a.o:a.c
	gcc -c -o a.o a.c
b.o:b.c
	gcc -c -o b.o b.c

3.规则二

当“目标文件”不存在
或者
某个依赖文件比目标文件新,则
执行命令

二、makefile的基本语法

1. 通配符

%.o:表示所用的.o文件
%.c:表示所有的.c文件(注意Linux命令行中的通配符是*)
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件
@:加在命令前面可以在make的过程中不显示该命令

例二:

test:a.o b.o
	gcc -o $@ $^
%.o:%.c
	gcc -c -o $@ $<

2. 假想目标: .PHONY

a、我们想清除文件,我们在Makefile的结尾添加如下代码就可以了:

clean:
	rm *.o test

*1)执行 make :生成第一个可执行文件。
*2)执行 make clean : 清除所有文件,即执行: rm *.o test。
make后面可以带上目标名,也可以不带,如果不带目标名的话它就想生成第一个规则里面的第一个目
标。
b、但是上面这个写法有个问题,就是如果文件夹中有clean文件,那么就无法执行rm命令,并且会出现以下提示:

make: `clean’ is up to date.

这是因为makefile 的规则是:1、目标文件不存在 2、依赖文件更新
这种情况下目标文件已经存在而且没有依赖文件,make clean就不会执行rm命令
解决办法:在makefile文件中添加语句
.PHONY:clean # 把clean变成假想目标,这样就不会去判断clean文件是否存在

clean:
   rm *.o test
.PHONY:clean  # 把clean变成假想目标,这样就不会去判断clean文件是否存在

3. 变量

在makefile中有两种变量:简单变量(即时)和延时变量

1)简单变量:

在定义时即确定

A := 123    # A的值被确定,在定义时即确定

2)延时变量

在变量被第一次使用的时候才会变确定

B = 456  # B的值使用到时才确定

想使用变量的时候需要用$符号进行引用。
当我们执行make命令的时候,make这个指令本身,会把整个Makefile读进去,进行全部
分析,然后解析里面的变量。常用的变量的定义如下:

= # 延时变量 
:= # 即时变量 
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句 
+= # 附加, 它是即时变量还是延时变量取决于前面的定义 
?:= #如果这个变量在前面已经被定义了,这句话就会不会起效果,

例三:

A := $(C)  
B = $(C) 
C = abc 
#D = 100ask 
D ?= weidongshan 
all:
	@echo A = $(A) 
	@echo B = $(B) 
	@echo D = $(D) 

C += 123

运行结果:
韦东山嵌入式教程第四篇Linux基础知识学习笔记(1)——Makefile的使用_第1张图片
可以看到echo命令被隐藏了;A的值在定义时C的值没有被定义,所以结果为空;因为makefile是被整体读进去识别之后再执行命令,所以B的值被覆盖后的C值所定义;D的值之前没有被定义,如果被定义了则以第一次被定义时为准。

三、Makefile函数

1、函数foreach

函数foreach语法如下:

$(foreach var,list,text)

前两个参数,‘var’和‘list’,将首先扩展,注意最后一个参数 ‘text’ 此时不扩展;接着,对每一个 ‘list’ 扩
展产生的字,将用来为 ‘var’ 扩展后命名的变量赋值;然后 ‘text’ 引用该变量扩展;因此它每次扩展都不
相同。结果是由空格隔开的 ‘text’。在 ‘list’ 中多次扩展的字组成的新的 ‘list’。‘text’ 多次扩展的字串联起
来,字与字之间由空格隔开,如此就产生了函数 foreach 的返回值。
实例:

A = a b c
B = $(foreach x,$(A),$(x).o)  #把a,b,c变成a.o b.o c.o
all:
	@echo B = $(B)

结果:

2、函数filter/filter-out

语法规则

$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
  C = a b c d/
  D = $(filter %/,$(C))
  E = $(filter-out %/,$(C))
  all:
  	@echo D=$(D)
    @echo E=$(E)                                                                                                                                                                        

结果:
韦东山嵌入式教程第四篇Linux基础知识学习笔记(1)——Makefile的使用_第2张图片

3、wildcard函数

用于取出该文件下符合某种格式的变量(文件),比如可以取出所有后缀为.c的文件。语法:

$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出符合该格式的文件。

例子:

#首先在文件下面新建a.c  b.c  a.o b.o
files = $(wildcard *.c)  # 注意!!!这里是对文件夹中的文件进行操作,所以用*作为通配符(如果是对Makefile文件中的变量做操作,则使用%作为通配符)
all: 
	@echo files = $(files)

结果:
在这里插入图片描述
我们也可以用wildcard函数来判断,真实存在的文件


结果:
·files3 = a.c b.c c.c

4、patsubst函数

函数 patsubst 语法如下:

$(patsubst pattern, replacement, $(var)) #把var列表中符合pattern格式的值替换成replacement的值

例子:

files2 = a.c b.c c.c d.c e.c abc 
dep_files = $(patsubst %.c,%.d,$(files2))  #把.c 文件的后缀都改为 .d
all: 
	@echo dep_files = $(dep_files)

结果
dep_files = a.d b.d c.d d.d e.d abc

四、利用gcc自动生成依赖

(以下内容参考了CSDN博主「Jerry.yl」的原创文章,原文链接:https://blog.csdn.net/QQ1452008/article/details/50855810)

1、为何要使用.d文件

如果是一个比较大型的工程,我们必需清楚每一个源文件都包含了哪些头文件,并且在加入或删除某些头文件时,也需要一并修改 Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,可以使用 C/C++ 编译器的 “-M” 选项,即自动获取源文件中包含的头文件,并生成一个依赖关系。例如,执行下面的命令:

gcc -M main.c 	

其输出:main.o : main.c defs.h
这样一来通过编译器输出依赖文件的好处是

  • 不必手动书写若干目标文件的依赖关系, 由编译器自动生成
  • 不管是源文件还是头文件有更新,目标文件都会重新编译

.d文件就是编译器使用了-MD选项后生成的写有源文件需要的依赖文件的文件
有了.d文件就可以很方便的使用makefile了
e.g.
main.d:保存了 main.o 依赖关系的文件

2、gcc -M -MF -MD 等相关选项的说明

gcc -M c.c // 打印出依赖
gcc -M -MF c.d c.c // 把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d // 编译c.o, 把依赖写入文件c.d

具体参考原文第二部分:https://blog.csdn.net/QQ1452008/article/details/50855810

3、该博主推荐的makefile

SRCS=$(wildcard *.c) #SRCS意为源文件sources,用延迟赋值为文件夹中所有的.c文件名作为变量
OBJS=$(SRCS:.c=.o)	#OBJS意为目标文件objects,将SRCS中的.c变量替换为.o
DEPS=$(SRCS:.c=.d) 	#DEPS意为依赖文件,同理替换为.d

.PHONY: all clean	#虚拟目标,假想

all: main

-include $(DEPS)	#注释:'-'号的作用:加载错误时,会继续执行 make,
					#主要是考虑到首次 make 时,目录中若不存在 '*.d' 文件时,
					#加载便会产生错误而停止 make 的执行

%.o:%.c
	gcc -c -g -Wall $< -o $@ -MD -MF $*.d -MP  #注释: 
					#$* 表示目标模式中 '%' 及其之前的部分.如果目标是 'dir/a.foo.b',
					#并且目标的模式为 'a.%.b',那么 '$*' 的值就是 'dir/a.foo'。
					#如果目标中没有模式的定义,那么 '$*' 就不能被推导出;
					#但是,如果目标文件是 make 所识别的,那么 '$*' 就是除了后缀的那一部分。
					#例如:目标是 'foo.c',因为 '.c' 是 make 所能识别的后缀名,
					#所以 '$*' 的值就是 'foo',这个特性是 GNU make 的。
main: $(OBJS)
	gcc $^ -o $@   #注释:$^:表示所有的依赖文件 $@:表示目标文件

clean: 
	rm -f  *.d *.o main

五、CFLAGS

即编译参数。
例如可以加上

CFLAGS = -Werror -Iinclude # -Iinclude中第一个字幕是大写i而不是小写L

-Werror 表示把所有的警告当作错误
-I 可以指定头文件路径,例如 -I/xx/xx/xxx
-include用来包含头文件,但一般情况下包含头文件都在源码里用#include xxxxxx实现,-include参数很少用。
-Iinclude 表示将当前文件夹中的include文件夹作为第一个寻找头文件的目录(需要在当前文件夹中新建一个include文件夹用于存放所有的include文件)
-l 用于指定链接的库。例如-lm 指定连接数学库math,-lxxx就是去链接名字为xxx的库名

小tips:库的文件名通常为libxxx.so,这个xxx就是库名,例如math库的文件名叫libm.so,掐头去尾去掉lib和.so剩下的就是库名

而-L用于链接库文件所在的目录,通常用于链接自己安装不在默认路径下的库,直接加上路径即可。(库的默认路径通常为/usr/bin/ld)
更多好用的编译选项可以通过gcc文档去查找,与makefile本身没有关系

总结

易犯错误:
1、空命令前有tab,导致报错

你可能感兴趣的:(Linux基础,linux,运维)