Makefile使用详解[转载]

Makefile是一个C语言的编译工具。如果学过Java,可能会认识Maven工具,makefile也是类似的工作。

Makefile能帮助c语言建立自动化的编译。一旦写好,执行一个make命令就可以编译整个工程。当然编写Makefile文件的时候有很多知识点在里面。这篇文章主要讲解如何编写基础性以及常用的Makefile文件。


1. 没有makefile的编译

我们先看一个例子:

val.h和val.c

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. int val(int *x);  
[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include "val.h"  
  2.   
  3. int val(int *x) {  
  4.     puts("This is Value==");  
  5.     printf("X:%d \n", *x);  
  6.     return 0;  
  7. }  
get.h和get.c

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. int get(int *x, int *y);  
[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include "get.h"  
  2.   
  3. int get(int *x, int *y) {  
  4.     puts("This is get");  
  5.     return (*x) * (*y);  
  6. }  
sum.h和sum.c

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. int sum(int *x, int *y);  
[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include "sum.h"  
  2. #include "val.h"  
  3.   
  4. int sum(int *x, int *y) {  
  5.     val(x);  
  6.     puts("This is SUM Method!=========HDH");  
  7.     return *x + *y;  
  8. }  
main.c 入口文件

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include "sum.h"  
  5. #include "get.h"  
  6.   
  7.   
  8. //入口主函数  
  9. int main() {  
  10.     int x = 10;  
  11.     int y = 20;  
  12.     int z = sum(&x, &y);  
  13.     puts("This is Main");  
  14.     printf("Z:%d\n", z);  
  15.     x = 20;  
  16.     z = get(&x, &y);  
  17.     printf("Z:%d\n", z);  
  18.     return 1;  
  19. }  

上面的例子中如果我们编译成功则需要:

1. 首先将val.c编译成中间目标文件 val.o

2. 将get.c编译成中间目标文件 get.o

3. 将sum.c编译成中间目标文件 sum.o

4. 将main.c编译成中间目标文件 main.o

5. 然后将上面的4个中间文件连接起来,编译成目标可执行文件 main

执行的命令如下:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. gcc -c val.c  
  2. gcc -c get.c  
  3. gcc -c sum.c  
  4. gcc -c main.c  
  5. gcc -o main main.o val.o sum.o get.o  

命令执行完之后,在文件夹目录下会多.o的文件和main 可执行文件。

执行 ./main 命令,输出:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. [admin@localhost test_c]$ ./main   
  2. This is Value==  
  3. X:10   
  4. This is SUM Method!=========HDH  
  5. This is Main  
  6. Z:30  
  7. This is get  
  8. Z:400  

那么问题来了,假如我的项目非常大,有1000个文件要编译怎么办?于是就有了makefile这样的编译工具。


2. 使用makefile编译c项目

Centos上首先要安装make的命令工具,可以通过yum的方式安装

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. yum install make  

然后我们编写一个makefile的文件的例子(文章后面都会以这个例子展开),其中文件名Makefile

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. main:get.o sum.o val.o main.o  
  2.         gcc -o main get.o sum.o val.o main.o  
  3. main.o:main.c  
  4.         gcc -c main.c  
  5. val.o:val.h val.c  
  6.         gcc -c val.c  
  7. sum.o:val.h sum.h sum.c  
  8.         gcc -c sum.c  
  9. get.o:get.h get.c  
  10.         gcc -c get.c  
  11. clean:  
  12.         rm -rf *.o  

然后我们在当前目录下 执行make命令

目录下发现已经有编译成功的main:

通过makefile文件,我们可以看到我们原先编译一个c程序,需要每一行命令都去敲,现在我们可以放到Makefile文件中,然后执行一下make命令,就能编译成功,这的确是令人比较兴奋的事情。但这仅仅是开始,makefile我们要学的东西很多,继续往下看。


3. 细说Makefile

1. Makefile的文件名称

Makefile的文件名称一般为:makefile或者Makefile,我们一般推荐Makefile,因为大写的首字母开头会比较引人注目。

当执行make工具命令的时候,工具会自动找到当前文件夹下Makefile或者makefile这个文件,然后执行里面的内容。


2. 目标可执行文件

先看下上面例子中的第一行:

[sql]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. main:get.o sum.o val.o main.o  

其中main 是目标可执行文件,也就是编译最后成功的文件名称。而冒号后面就是这个可执行文件需要依赖的.o的中间目标文件。

一般目标可执行文件都在文件最前面。


3. 中间目标文件

继续看上面例子中:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. main.o:main.c  
  2.         gcc -c main.c  
  3. val.o:val.h val.c  
  4.         gcc -c val.c  
  5. sum.o:val.h sum.h sum.c  
  6.         gcc -c sum.c  
  7. get.o:get.h get.c  
  8.         gcc -c get.c  
上面例子中,例如 get.o就是中间目标文件 ,而冒号后面是指生成这个get.o中间目标文件需要的依赖:get.h和get.c


4. 生成目标文件的命令

看例子中

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. main:get.o sum.o val.o main.o  
  2.         gcc -o main get.o sum.o val.o main.o  
发现第二行是一行gcc的命令,这行命令代表的意思是将各种中间目标文件连接起来,生成最终的目标可执行文件 main

主要特别注意的是,命令行都是在第二行,而且需要[tab]键的空格,否则就会不生效。


5. 换行符 \

如果一行太长,我们可以通过 符号 \ 来进行换行。

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. main:get.o sum.o val.o \  
  2.  main.o  

6. clean 清除操作

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. clean:  
  2.         rm -rf *.o  
clean不是一个目标文件,而是一个动作命令。我们可以执行下面的命令

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. make clean  
这个命令会清除生成的目标中间文件 .o的文件。


7. 执行流程

1. 当用户敲下make命令的时候,make工具会去找当前c工程目录下的Makefile或者makefile这个文件。

2. 找到Makefile这个文件后,他会找到第一个目标文件(main),然后去当前c工程的目录下找是否有main这个可执行的文件,然后会去判断main这个可执行文件是否是最新的,如果是最新的,则不需要编译。

3. 如果不存在main这个可执行文件,make命令就会去生成.o的中间目标文件,然后再生成main可执行目标文件。

4. 如果main存在,make会去比较中间文件.o文件是否有更新,如果是最新的则不更新,不是最新的则继续往下找依赖,找到有需要更新的目标文件的时候就会执行编译命令。

5. 说白了,整个make的依赖性,make会一层层的去找依赖关系,并且比较是否需要更新,直到编译出最终的可执行的目标文件。

举个例子,如果我在上面的c语言程序例子中,只改动main.c这个文件,则执行make命令,命令只会编译main.o和连接生成main可执行文件两步,而不是全部都重新编译一遍。因为val.o get.o 和sum.o并没有改动,而且是最新的,所以不需要编译。如果项目比较大,这样的方式可以加快编译速度。


8. 变量使用

makefile中也可以使用变量。变量可以用 ${object} 或者 $(object)这样的方式使用。

使用变量的好处就是大大简化了Makefile编写的难度,增强了Makefile文件的可维护性。

我们改造上面的Makefile例子:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  
  3. main:${object}  
  4.         gcc -o main ${object}  
  5. main.o:main.c  
  6.         gcc -c main.c  
  7. val.o:val.h val.c  
  8.         gcc -c val.c  
  9. sum.o:val.h sum.h sum.c  
  10.         gcc -c sum.c  
  11. get.o:get.h get.c  
  12.         gcc -c get.c  
  13. clean:  
  14.         rm -rf *.o  


9. make的自动推导功能

我们上面的例子中可以看到:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. main.o:main.c  
  2.         gcc -c main.c  
  3. val.o:val.h val.c  
  4.         gcc -c val.c  
  5. sum.o:val.h sum.h sum.c  
  6.         gcc -c sum.c  
  7. get.o:get.h get.c  
  8.         gcc -c get.c  
每次我们写一个中间目标文件的时候,都需要将相应的依赖关系写出来,然后写上gcc -c xxx.c的命令。

make工具有一个自动推导的功能:

例如sum.o文件,make会自动推导出依赖的文件 sum.c,于是在sum.o冒号后面就可以将sum.c隐藏

并且有了sum.c的推导,make工具可以可以推导出下面的gcc命令:gcc -c sum.c。于是这个gcc的命令行也可以隐藏了。

于是我们继续改进上面的例子,会发现现在简化了很多:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  
  3. main:${object}  
  4.         gcc -o main ${object}  
  5. main.o:get.h sum.h  
  6. val.o:val.h  
  7. sum.o:val.h sum.h  
  8. get.o:get.h  
  9. clean:  
  10.         rm -rf *.o  


10. include引用其他文件

通过include <filename>,可以将别的Makefile文件引入进来。

例如我们object这个变量内容放入test.vm文件中:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  
看下Makefile文件的使用:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. include test.vm  
  2. main:${object}  
  3.         gcc -o main ${object}  
  4. main.o:get.h sum.h  
  5. val.o:val.h  
  6. sum.o:val.h sum.h  
  7. get.o:get.h  
  8. clean:  
  9.         rm -rf *.o  

这个时候,我们执行make命令,发现Makefile和之前都放置在一起的时候是一样的效果。


11. .PHONY 声明伪目标

可以用.PHONY来声明clean这样的不生成可执行文件的伪目标。可以看下Makefile改造后的例子:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  
  3. main:${object}  
  4.         gcc -o main ${object}  
  5. main.o:get.h sum.h  
  6. val.o:val.h  
  7. sum.o:val.h sum.h  
  8. get.o:get.h  
  9. .PHONY: clean cleanall  
  10. clean:  
  11.         rm -rf *.o  
  12. cleanall:clean  
  13.         rm -rf main  
其中cleanall 既删除main文件,也执行clean下的命令,因为cleanall依赖于 clean伪目标。


12. 生成多个可执行文件

如果要生成多个可执行文件怎么办?大家都知道,Makefile的终极生成的目标文件只有一个,多个可以通过依赖的方式来进行生成:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  
  3. main:test ${object}  
  4.         gcc -o $@ ${object}  
  5. test:${object}  
  6.         gcc -o $@ ${object}  
  7. main.o:get.h sum.h  
  8. val.o:val.h  
  9. sum.o:val.h sum.h  
  10. get.o:get.h  
  11. .PHONY: clean cleanall  
  12. clean:  
  13.         rm -rf *.o  
  14. cleanall:clean  
  15.         rm -rf main  



13. 常用变量和符号

$(MAKE) :是make命令

$(CC) :是gcc的命令

$@:可以获取到当前的目标

@echo: 这个命令是输出一行字符串。make -s 则只显示echo输出的命令,不显示执行的命令行。

如果是多条命令的,可以通过换行来实现;如果是两条命令有关联,可以通过;号来分隔。

忽略命令的出错情况,可以在命令前面加 - 符号,例如:-rm -rf *.o

可以看下例子:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  
  3. main:$(object)  
  4.         @echo it is compile main.c now!..............  
  5.         $(CC) -o $@ $(object)  
  6.         $(MAKE) clean  
  7. main.o:get.h sum.h  
  8. val.o:val.h  
  9. sum.o:val.h sum.h  
  10. get.o:get.h  
  11. .PHONY: clean cleanall  
  12. clean:  
  13.         -rm -rf *.o  
  14. cleanall:clean  
  15.         -rm -rf main  

定义变量:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. object = get.o sum.o val.o \  
  2. main.o  

使用变量:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. $(object)  
  2. foo = $(object) test.o  

if语句,未定义:undefined

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. ifeq (0,${MAKELEVEL})  
  2.     mkdir test_path  
  3. endif  

空格使用:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. nullstring :=  
  2. space := $(nullstring)  



14. 嵌套执行Makefile

有些情况下,我们会把Makefile 不同模块和不同功能的源文件放到不同的目录下,这个时候我们可以通过:

@(make) -C src/   这样的命令来执行。

这个命令相当于先进入src目录,再执行make命令。

如果想让你的变量传递到下一层的Makefile中,则可以通过下面的命令export,如果不想传递则unexport

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. export variable = value  

15. 定义命令包

如果命令经常使用到,可以定义命令包:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. define test  
  2. mkdir test  
  3. endef  
使用:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. $(test)  


16. 文件搜寻

当项目比较大的时候,源文件会按照不同的目录去分类,这个时候就需要通过VPATH来搜索。VPATH是makefile的一个特殊变量,定义了源文件的存放目录。

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. VAPTH=src:../header  

上面的意思就是回去src目录和../header目录查询源文件。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值