# 用 make 进行宏编译

# 为什么要用 make

来看一个案例:执行文档里面包含了 4 个源码文件,分别是 main.chaha.csin_value.ccos_value.c

  • main.c:主要目的是让用户输入角度数据与调用其他三个子程序
  • haha.c:输出一堆有的没有的信息
  • sin_value.c:计算使用者输入的角度(360)sin 数值
  • cos_value.c:计算使用者输入的角度(360)cos 数值

main.c

#include <stdio.h>
#define pi 3.14159
char name[15];
float angle;

int main(void)
{
	printf ("\n\nPlease input your name: ");
	scanf  ("%s", &name );
	printf ("\nPlease enter the degree angle (ex> 90): " );
	scanf  ("%f", &angle );
	haha( name );
	sin_value( angle );
	cos_value( angle );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

haha.c

#include <stdio.h>
int haha(char name[15])
{
	printf ("\n\nHi, Dear %s, nice to meet you.", name);
}

1
2
3
4
5
6

sin_value.c

#include <stdio.h>
#include <math.h>
#define pi 3.14159
float angle;

void sin_value(void)
{
	float value;
	value = sin ( angle / 180. * pi );
	printf ("\nThe Sin is: %5.2f\n",value);
}

1
2
3
4
5
6
7
8
9
10
11
12

cos_value.c

#include <stdio.h>
#include <math.h>
#define pi 3.14159
float angle;

void cos_value(void)
{
	float value;
	value = cos ( angle / 180. * pi );
	printf ("The Cos is: %5.2f\n",value);
}

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 先进行目标文件的编译,最终会有 4 个 *.o 的文件出现
[root@study ~]# gcc -c main.c 
[root@study ~]# gcc -c haha.c 
[root@study ~]# gcc -c sin_value.c 
[root@study ~]# gcc -c cos_value.c 

# 2. 再执行连结称为执行文件,并加入 libm 的函数,产生 man 的执行文件
[root@study ~]# gcc -o man main.o haha.o sin_value.o cos_value.o  -lm

# 3. 执行程序,比如输入姓名,360 度角的角度值来计算
[root@study ~]# ./man 


Please input your name: mrcode		# 输入姓名
	
Please enter the degree angle (ex> 90): 30		# 输入角度


Hi, Dear mrcode, nice to meet you.
The Sin is:  0.50
The Cos is:  0.87
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

可以看到编译指令就变得复杂起来了,如果要重新编译,上述流程还需要重新来一次,很麻烦

可以使用 make 工具简化我们的工作。先建立 makefile 的文件

# 1. 先编辑 makefile 规则文件,内容只要做出 man 这个执行文件
[root@study ~]# vim makefile
main: main.o haha.o sin_value.o cos_value.o
        gcc -o main main.o haha.o sin_value.o cos_value.o -lm
# 注意第 2 行数据,是按 tab 产生的空格

# 2.尝试使用 makefile 指定的规则进行编译行为
# 先把之前产生的 .o 文件删除
[root@study ~]# rm -f main *.o
[root@study ~]# make          
cc    -c -o main.o main.c
cc    -c -o haha.o haha.c
cc    -c -o sin_value.o sin_value.c
cc    -c -o cos_value.o cos_value.c
gcc -o main main.o haha.o sin_value.o cos_value.o -lm

# 此时 make 会读取 makefile 的内容,并根据内容直接编译相关的文件

# 3. 在不删除任何文件的情况下,重新执行一次编译的动作
[root@study ~]# make
make: `main' is up to date.
# 只会执行更新 update 的动作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上面的执行,从命令行输出来看,获取你会觉得 shell script 也可以做到,的确是这样,但是 make 提供了增量编译的机制,不需要你自己去写那么复杂的流程判断了。好处如下:

  • 简化编译时所需要下达的指令
  • 若在编译完成之后,修改了某个源码文件,则 make 仅会针对被修改了的文件进行编译,其他的 object file 不会变动
  • 最后可以依照相依性来更新(update)执行文件

下面针对 makefile 的语法来介绍

# makefile 的基本语法与变量

makefile 的语法多而复杂,可以参考 GUN 官网文档,这里仅做一些基本的规则,重点在于你接触源码的时候,不至于恐慌,基本规则如下

目标(target):目标文件1 目标文件2
<tab>	 gcc -o 欲建立的执行文件 目标文件1 目标文件2
1
2

在 makefile 中的规则基本上是:

  • # 代表批注
  • <tab> 需要在命令行的第一个字符
  • 目标 target 与相依文件(目标文件)之间以 : 分割

对上上面那个示例,如果有两个以上的执行动作时,如何做?比如增加一个指令,直接清除所有的目标文件与执行文件

# 1. 编辑 makefile 来建议新的规则,此规则的目标为 clean
[root@study ~]# vim makefile 
main: main.o haha.o sin_value.o cos_value.o
        gcc -o main main.o haha.o sin_value.o cos_value.o -lm
clean:
        rm -rf main.o haha.o sin_value.o cos_value.o
        
# 2. 以新的目标 clean 测试执行
[root@study ~]# make clean
rm -rf main.o haha.o sin_value.o cos_value.o
1
2
3
4
5
6
7
8
9
10

如此一来,makefile 里就具有至少两个目标,可以单独执行,也可以如下一起组合执行

[root@study ~]# make clean main
rm -rf main.o haha.o sin_value.o cos_value.o
cc    -c -o main.o main.c
cc    -c -o haha.o haha.c
cc    -c -o sin_value.o sin_value.c
cc    -c -o cos_value.o cos_value.c
gcc -o main main.o haha.o sin_value.o cos_value.o -lm
1
2
3
4
5
6
7

这个时候,你会发现 makefile 中的重复数据很多,可以通过变量来重构

[root@study ~]# vim makefile 
LIBS = -lm
OBJS = main.o haha.o sin_value.o cos_value.o
main: ${OBJS}
        gcc -o main ${OBJS} ${LIBS}
clean:
        rm -rf main ${OBJS}
# 一定要注意这个 tab 键的语法,不能使用空格替代的!
1
2
3
4
5
6
7
8

与 bash shell script 的语法有点不太相同,变量的基本语法为:

  1. 变量与变量内容以 = 隔开,同时两边可以有空格
  2. 变量左边不可以有 <tab>
  3. 变量与变量内容在 = 两边不能有 :
  4. 在习惯上,变量最好以大写字母为主
  5. 使用变量时,以 ${变量}、$(变量)使用
  6. 在该 shell 的环境变量是可以被套用的,例如提到的 CFLAGS 这个变量
  7. 在指令列模式也可以给予变量

由于 gcc 在编译的行为时,会主动读取 CFLAGS 这个环境变量,所以可以直接在 shell 定义出这个环境变量

CFLAGS="-Wall" make clean main
# 在 make 进行编译时,会读取 CFLAGS 的变量内容

# 还可以在 makefile 中定义这个变量
1
2
3
4

如果在指令列和 makefile 中都设置了 CFLAGS 变量,而且内容不同,那么哪一个会生效?

  1. make 指令列后加上的环境变量为优先
  2. makefile 里面指定的环境变量第 2
  3. shell 原本具有的环境变量第 3

此外:$@ 这个特殊的变量,表示目前的目标(target),因此可以修改 makefile 为

[root@study ~]# vim makefile 
LIBS = -lm
OBJS = main.o haha.o sin_value.o cos_value.o
main: ${OBJS}
        gcc -o $@ ${OBJS} ${LIBS}		
        # 这里的 $@ 就表示是 main 这个字符
clean:
        rm -rf main ${OBJS}
1
2
3
4
5
6
7
8