Makefile之清晰认知
Makefile之清晰认知
关于Makefile,之前我在初学makefile依旧没有清晰认知。所以这次,我将着重探究一下Makefile文件的编写。
0.Makefile是什么
Makefile可以理解为一个脚本的执行器,也可以理解为一种简单语言
很多人觉得很神奇,包括我也是,第一次接触makefile,觉得这个东西非常神奇,为什么可以自动编译多文件?
我们可以简单理解为这个文件可以自动帮你执行一些命令,例如Python也可以帮你做运维,这个就类似于运维中的脚本文件
1.一般使用
# This is a standard makefile
main:main.o module1.o module2.o
gcc main.o module1.o module2.o -o main
main.o:main.c head1.h head2.h common_head.h
gcc -c main.c
module1.o:module1.c head1.h
gcc -c module1.c
module2.o:module2.c head2.h
gcc -c module2.c
结构为
[目标文件列表] [分隔符] [依赖文件列表]
[命令]
这个Makefile的HelloWorld其实已经困扰了很多人。困扰的点如下:
1.为什么gcc编译的时候不需要加头文件?
答:gcc在编译的时候,只需要进行编译C文件。
h文件的作用仅起到替换的作用。我们在引入#include <stdlib.h>之后,在预编译的过程中,会将stdlib.h中的声明完全替换到本文件中。
误解:很多人会将#include以为是引入库,但实际不是,这个命令的作用是在预编译过程中将.h文件进行替换,真正要编译的是C文件。
证明:我们使用gcc -E main.c试一下,查看预编译后的文件,发现,头文件已经替换到了预编译后的文件中
编写一个max.h文件
int max(int a, int b);
编写一个main.c文件
#include <max.h>
int main() {
return 0;
}
编写一个max.c文件
int max(int a, int b) {
return a > b ? a : b;
}
在Linux下进行gcc -E main.c max.c,输出
# 1 "t.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 362 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "t.c" 2
# 1 "./max.h" 1
int max(int a, int b);
# 2 "t.c" 2
int main() {
return 0;
}
# 1 "max.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 362 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "max.c" 2
int max(int a, int b) {
return a > b ? a : b;
}
在预编译过程中,进行了文件替换,在#include语句完全替换成了h文件中的语句,而在文件末也附加了新的max.c,由此我们可以深刻理解这个过程
2.gcc -c是什么?
gcc -c 执行了3步操作
gcc -E进行预处理,gcc -S进行翻译汇编操作,gcc -c进行编译成链接文件(.o文件)
3.我们为什么需要倒着写?
问这个问题的人应该已经发现了,我们是需要倒着进行运行命令的,这个是makefile的规范。
如果熟悉如何用gcc在命令行进行编译多文件,那么直接进行倒着书写makefile即可。
makefile在进行寻找的过程中,首先执行第一行,发现需要下面模块,再执行下面的,所以看起来倒着,假设a依赖b,c,d三个独立模块,我们测试进行输出,答案是b,c,d,a,案例:
# This is a Makefile
main:main.o printXX.o printXXX.o
gcc main.o printXX.o printXXX.o
echo "a"
main.o:main.c printXX.h printXXX.h
gcc -c main.c
echo "b"
printXX.o:printXX.c printXX.h
gcc -c printXX.c
echo "c"
printXXX.o:printXXX.c printXXX.h
gcc -c printXXX.c
echo "d"
4.第二行报错?
实际上gcc前面那个空格是一个tab键,如果你按了2下空格,会使其报错。
如果你想简单实用makefile,了解到这边,其实已经能够清晰的编写makefile文件了。下面是附加性知识。
2.语法
1.变量的定义
我们在使用过程中,会使用到大量的gcc命令,而实际生产中,我们不一定会用gcc进行编译,常见的有cc,g++,都可以进行编译,如果要进行替换,整篇Makefile都得进行替换,那样会极其不便,所以,我们进行了一个变量替换操作。
我们来看一个例子:
CC = gcc
all:
echo $(CC)
我们在头部进行了替换操作。
2.预定义变量
all:
echo $(CC) $(CFLAGS) $(MAKE)
我们的cc含有很多预定义变量,如CC、CFLAGS等,我们可以编写一个这样的文件进行打印
预定义变量如下:
-
CC 默认的cc命令
-
CFLAGS -o执行
-
MAKE make命令的地址
-
MAKEFLAGS make的选项
-
SHELL 默认shell类型
-
PWD 当前makefile的目录
-
AR 库管理
-
ARFLAGS 库管理选项
-
LIBSUFFIXE 库后缀
-
A 库拓展名
3.动态改变的变量
我们在查看Makefile的时候,常常看到$@ $% $< $> $? $^ $+ $*
- $@ 表示目标文件名,Linux中,一般称.a文件为文档文件,或者静态库文件。
main: a.c b.h
gcc a.c
上述makefile的main就是目标文件名,如果我们在makefile中echo $@,则打印的是main
所以我们可以进行
main: a.c b.h
gcc a.c -o %@
- $% 静态库的第一个成员名,如果链接的第一个静态库是foo.a,则会打印出来,这边不加演示,都可进行echo打印
- $< 第一个依赖库名
- $> 值是库名(适用库文件)
- $? 所有比目标新的依赖文件列表
- $^ 规则的所有依赖列表,去除重复文件
- \(+ 可素\)^ 保留重复文件
- $* 目标去除后缀,例如 main.o 这个后缀去掉
4.条件语句
ifeq(gcc,$(CC))
# 执行编译
else
echo "error"
endif
条件语句可以根据条件进行编译
5.定义条件
ifdef foo
echo "YES"
ifndef foo
foo = foo
定义条件,判定是否定义