Makefile 学习笔记:

Makefile 学习笔记:



为什么要学习makefile :
工作经验让我认识到 会不会写makefile从一个 侧面说明了一个人是否有完成大型工程的能力,makefile 关系到整个工程的编译规则,一个工程的文件不计其数,其按类型,功能,模块分别放在不同的目录下,makefile定义了一些规则来指定,哪些文件需要先编译,哪些文件需要重新编译,甚至进行更复杂的功能操作,因为makefile就像一个shell脚本一样,其中也可以执行操作系统命令。
而 make 只是一个命令工具,是一个解释makefile中的指令的命令工具,一般来说IDE即集成开发环境都有这个命令。
Makefile 的环境:
我是在linux下进行的实验 linux 系列下, 我用的是ubuntu ,当然你可以用redhat 红旗后其他,我想都没有什么问题的。在做实验的时候我会做一些linux写的c/c++例子来演示,以加深理解。
关于程序的编译和链接:
一般来说,c或者是c++,首先把源文件 (*.c 或*.cpp)编译成为中间代码文件,这个中间代码文件在windows下是*.obj文件在linux 或unix 是*.o文件即object file 目标文件这个动作就叫做编译,即把源文件编译成目标文件的过程就叫做编译(compile)。这以后,再把大量的*.obj 或 *.o 目标文件合成一个可以执行的文件,这个工程就叫做链接link。编译时,主要是检查程序的语法是否正确,函数,变量是否都有声明。至于链接呢,主要是链接函数,和全局变量。
一. Makefile 的规则
Target:prerequisites
Command
。。。
。。。
。。。
Target就是一个目标文件,可以使obj或是可执行文件还可以是一个标签label,关于标签label会在下面的文目标中讲解。所以我们现在只关注obj 和可执行文件即可,其实大部分还都是obj文件,或许可执行文件就只有一个。
Prerequisites 是先决条件的意识,其实在这里只是依赖的意思,prerequisites在这里是生成target的所需要的文件或目标。Command 就是make 需要执行的命令的,任意的shell的命令,如果prerequisites中有一个以上的文件比target文件要新的话吗,command所定义的命令的就会被执行。这就是makefile的规则,也是makefile中最核心的东西。
一个例子:
这个makefile有六个源文件 和六个头文件 分别是:


func1.c  func2.c  func3.c  func4.c  func5.c  main.c
head1.h  head2.h  head3.h  head4.h head5.h  head.h
上面的c源文件 分别会用到其下的 头文件 各个文件的内容分别是:
func1.c 文件
#include "head.h"
#include "head1.h"
void f1()
{
struct student1 stu;
stu.id = 10101;
strcpy(stu.name,"ygt1");
stu.sex = 'm';
printf("id = %d\t name = %s\t sex = %c\n",stu.id,stu.name,stu.sex);
}
func2.c 文件
#include "head.h"
#include "head2.h"
void f2()
{
struct student2 stu;
stu.id = 10102;
strcpy(stu.name,"ygt2");
stu.sex = 'm';
printf("id = %d\t name = %s\t sex = %c\n",stu.id,stu.name,stu.sex);
}
func3.c 文件
#include "head.h"
#include "head3.h"
void f3()
{
struct student3 stu;
stu.id = 10103;
strcpy(stu.name,"ygt3");
stu.sex = 'm';
printf("id = %d\t name = %s\t sex = %c\n",stu.id,stu.name,stu.sex);
}
func4.c 文件
#include "head.h"
#include "head4.h"
void f4()
{
struct student4 stu;
stu.id = 10104;
strcpy(stu.name,"ygt4");
stu.sex = 'm';
printf("id = %d\t name = %s\t sex = %c\n",stu.id,stu.name,stu.sex);
}
func5.c 文件
#include "head.h"
#include "head5.h"
void f5()
{
struct student5 stu;
stu.id = 10105;
strcpy(stu.name,"ygt5");
stu.sex = 'm';
printf("id = %d\t name = %s\t sex = %c\n",stu.id,stu.name,stu.sex);
}


main.c 文件
#include "head.h"
extern void f1();
extern void f2();
extern void f3();
extern void f4();
extern void f5();
int main()
{
f1();
f2();
f3();
f4();
f5();
printf("the end\n");
return 0;
}


以上是这个工程的的所有源文件及其代码
head1.h 头文件
struct student1
{
int id;
char name[20];
char sex;
};


head2.h 头文件
struct student2
{
int id;
char name[20];
char sex;
};


head3.h 头文件
struct student3
{
int id;
char name[20];
char sex;
};


head1.h 头文件
struct student3
{
int id;
char name[20];
char sex;
};


head4.h 头文件
struct student4
{
int id;
char name[20];
char sex;
};


head51.h 头文件
struct student5
{
int id;
char name[20];
char sex;
};


head.h 头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
以上是头文件的内容
以上文件都准备好后 就要开始写makefile 文件了




Makefile 文件的可以这么写:






exefile: main.o func1.o func2.o func3.o func4.o func5.o 
gcc -o exefile main.o func1.o func2.o func3.o func4.o func5.o
main.o:main.c head.h 
gcc -c main.c
func1.o:func1.c head.h head1.h
gcc -c func1.c
func2.o:func2.c head.h head2.h
gcc -c func2.c
func3.o:func3.c head.h head3.h
gcc -c func3.c
func4.o:func4.c head.h head4.h
gcc -c func4.c
func5.o:func5.c head.h head5.h
gcc -c func5.c




clean:
rm -f *.o exefile
在这个makefile中 蓝色的就是目标文件 或 可执行文件 目标文件时那些 *.o文件, 可执行文件就是exefile 文件,它是最终可以执行的文件。依赖文件就是那些*.c  和 *.h 文件。
每一个*.o 文件都有一组依赖文件,*.o 文件就是靠这些依赖文件生成。而生成的这些*.o文件又都是 exefile 可执行文件的依赖文件,他们生成 exefile 可执行文件。依赖关系 其实就是 说明了 目标文件是由哪些文件生成的,换言之,就是目标文件是由哪些文件更新的。
定义好依赖关系下一行就是make 执行的命令,这个命令定义了操作系统如何生成这些目标文件的。命令行开始一定要以tab键开头。其执行过程就是 make会比较 target 文件 和 prerequisites 文件 的修改日期,如果prerequisites 文件的日期比target文件的日期要新,或者target 不存在的话,make就会执行后续定义的命令。


讲解:
exefile: main.o func1.o func2.o func3.o func4.o func5.o 
gcc -o exefile main.o func1.o func2.o func3.o func4.o func5.o


exefile 依赖main.o func1.o func2.o func3.o func4.o func5.o这些*.o文件,也即是说exefile 就是开这些文件生成的。一开始这些*.o文件是不存在的,那么make就会往下执行语句,而暂时先不执行gcc -o main main.o func1.o func2.o func3.o func4.o func5.o 这句命令。


main.o:main.c head.h 
gcc -c main.c
main.o 依赖main.c head.h这两个文件 执行其命令生成 main.o目标文件,指着往下执行。。。


func1.o:func1.c head.h head1.h
gcc -c func1.c
同 main.o 的执行。。。
func2.o:func2.c head.h head2.h
gcc -c func2.c
同 main.o 的执行。。。


func3.o:func3.c head.h head3.h
gcc -c func3.c
同 main.o 的执行。。。


func4.o:func4.c head.h head4.h
gcc -c func4.c
同 main.o 的执行。。。


func5.o:func5.c head.h head5.h
gcc -c func5.c
同 main.o 的执行。。。
当这些 *.o文件都别生成了后 make 就会执行 第一个依赖和第一个 依赖之后的命令
exefile: main.o func1.o func2.o func3.o func4.o func5.o 
gcc -o exefile main.o func1.o func2.o func3.o func4.o func5.o
最终生成 exefile 之可行文件。


clean:
rm -f *.o exefile
clean  后面没有依赖文件,make 是不会执行 其后的命令的 ,只能make clean 显视 的执行。这句就是伪命令,就是做一些清理,把 生成的 目标文件 *.o文件 和 exefile 删掉。
Make 后执行结果:
[yanggentao@wkp mfile]$ make clean
rm -f *.o exefile
[yanggentao@wkp mfile]$ make
gcc -c main.c
gcc -c func1.c
gcc -c func2.c
gcc -c func3.c
gcc -c func4.c
gcc -c func5.c
gcc -o main main.o func1.o func2.o func3.o func4.o func5.o
[yanggentao@wkp mfile]$


Make clean 后执行结果:
[yanggentao@wkp mfile]$ make clean
rm -f *.o exefile
[yanggentao@wkp mfile]$
根据makefile 的依赖规则我们还可以这样写,至于为什么这样写,我们先且不说。
exefile: main.o func1.o func2.o func3.o func4.o func5.o 
gcc -o exefile main.o func1.o func2.o func3.o func4.o func5.o
main.o:main.c 
gcc -c main.c
func1.o:func1.c 
gcc -c func1.c
func2.o:func2.c 
gcc -c func2.c
func3.o:func3.c 
gcc -c func3.c
func4.o:func4.c 
gcc -c func4.c
func5.o:func5.c 
gcc -c func5.c




clean:
rm -f *.o exefile


这样写是把头文件都给去掉了,这样也对的,makefile的隐式规则会自动找这些在文件里包含的头文件的。


其实 Makefile 中的命令就像是shell 里一样 可以使用变量
二. Makefile 中使用变量
Makefile中的变量就像是c 语言的中宏一样
怎样定义变量呢?
我们在makefile最上面定义 一个变量
OBJS = main.o func1.o func2.o func3.o func4.o func5.o
引用变量 $(OBJS) 这就等价于main.o func1.o func2.o func3.o func4.o func5.o 就像宏一样的会被替换掉。所以我们的makefile 可以这样写了:


OBJS = main.o func1.o func2.o func3.o func4.o func5.o


exefile: $(OBJS)
gcc -o exefile $(OBJS)
main.o:main.c 
gcc -c main.c
func1.o:func1.c 
gcc -c func1.c
func2.o:func2.c 
gcc -c func2.c
func3.o:func3.c 
gcc -c func3.c
func4.o:func4.c 
gcc -c func4.c
func5.o:func5.c 
gcc -c func5.c




clean:
rm -f $(OBJS) exefile
 
这样写很方便,如果你想在这个工程里面加一个文件的话就不会很麻烦。
三. 让make自动推导
Gnu 的make 功能很强大他可以自动推导文件及文件的依赖关系后面的命令所以我们没有必要去为*.O文件都写出其命令。
只要make看到一个*.o文件,它就会自动的吧 *.c文件 加到 依赖关系中,如果make 找到一个 func2.o 那么func2.c 就会使func2.o 的依赖文件。并且gcc –c func2.c 也会被推导出来。所以我们的makefile就会简单多了:
我们还可以这样写:


OBJS = main.o func1.o func2.o func3.o func4.o func5.o


exefile: $(OBJS)
gcc -o exefile $(OBJS)
clean:
rm -f $(OBJS) exefile 






有时候我们会这样写:
OBJS = main.o func1.o func2.o func3.o func4.o func5.o
exefile: $(OBJS)
gcc -o exefile $(OBJS)
.PHONY:clean
clean:
rm -f $(OBJS) exefile 


.PHONY:clean 是声明一下clean是一个伪命令。


到这里makefile的基本东西已经讲完了。还有很多细节,下面来看一下。
四. Makefile 的5个内容
1. 显示规则
2. 隐晦规则
3. 变量的定义
4. 文件指示
5. 注释
1. 显示规则:就是显示的在命令行中写出目标文件的依赖关系
2. 隐晦规则:就是利用make 的 自动推导的功能
3. 变量的定义:就变量的宏替换
4. 文件指示:其中包括三部分的内容,一个是在一个makefile中引用另一个makefile,就像c语言中的include 一样;另一个是根据某些情况指定makefile中的有效部分,就像c语言的预编译#ifdef一样;还有一个就是定义一个多行的命令。
5. 注释:只有行注释用#号字符注释 如果你的makefile中用到了# 你可以用“\#“转义


Makefile 的文件名 默认会找 这三个文件 GNUmakefile ,makefile 和 Makefile
当然你也可以任意起名字 比如 linuxmakefile  mymakefile 等但是 如果要用的话,那么你就要指定它 这样用 make –f  linuxmakefile  或 make –f mymakefile


引用其他的makefile 
 Makefile中也有include 命令,这个命令就像c c++里的#include 关键字一样包含
Include 的语法是
Include <filename>










伪目标:
先前的一个例子:
Clean: 
Rm –f *.o exefile
Clean 就是一个伪目标  因为呢,clean并不是一个文件只是一个标签,所以make 无法生成它的依赖关系和决定是否要执行它的命令,所以呢我们只能通过显示的指明这个目标才能让其生效。当然为目标的取名不能喝文件名同名,不然就失去了为目标的意义了。
所以我们可以用.PHONY来显示的指明一个伪目标向make 说明不管是否有这个文件,这个目标就是伪目标。
.PHONY:clean
Clean:
Rm –f *.o




伪目标没有依赖关系但是我们可以为他指定依赖文件。
例子:这个例子可以生成三个可执行文件
先来看:
在linux下创建 touch a.c b.c c.c main1.c main2.c main3.c a.h b.h c.h main.h 执行此命令就会创建好所需要的文件,每个文件内容如下:
a.c 文件
#include "main.h"
#include "a.h"
void fa()
{
struct studenta stud;
stud.id = 10101;
strcpy(stud.name,"a.c");
stud.sex = 'f';
printf("studenta-->id = %d\tname = %s\t sex = %c\n",stud.id,stud.name,stud.sex);
}


b.c 文件
#include "main.h"
#include "b.h"
void fb()
{
struct studentb stud;
stud.id = 10101;
strcpy(stud.name,"b.c");
stud.sex = 'f';
printf("studentb-->id = %d\tname = %s\t sex = %c\n",stud.id,stud.name,stud.sex);
}
c.c 文件
#include "main.h"
#include "c.h"
void fc()
{
struct studentc stud;
stud.id = 10101;
strcpy(stud.name,"c.c");
stud.sex = 'f';
printf("studentc-->id = %d\tname = %s\t sex = %c\n",stud.id,stud.name,stud.sex);
}
main1.c 文件
#include "main.h"


extern void fa();
extern void fb();
extern void fc();
int main()
{
fa();
//fb();
// fc();
printf("in the main1\n");
return 0;
}


main2.c 文件
#include "main.h"
extern void fa();
extern void fb();
extern void fc();
int main()
{
fa();
fb();
fc();
printf("in the main2\n");
return 0;
}
main3.c 文件
#include "main.h"
extern void fa();
extern void fb();
extern void fc();
int main()
{
fa();
fb();
fc();
printf("in the main3\n");
return 0;
}
main.h 文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
a.h 文件
struct studenta
{
int id;
char name[20];
char sex;

};
b.h 文件
struct studentb
{
int id;
char name[20];
char sex;

};
c.h 文件
struct studentc
{
int id;
char name[20];
char sex;

};
我们的makefile先这样写:
main1:main1.o a.o b.o c.o
gcc -o main1 main1.o a.o b.o c.o
main2:main2.o a.o b.o c.o
gcc -o main2 main2.o a.o b.o c.o
main3:main3.o a.o b.o c.o
gcc -o main3 main3.o a.o b.o c.o
看看执行结果是什么 why?
执行结果 只执行了第一个 生成了main1 why;
正确的写法:
all:main1 main2 main3
.PHONY:all
main1:main1.o a.o b.o c.o
gcc -o main1 main1.o a.o b.o c.o
main2:main2.o a.o b.o c.o
gcc -o main2 main2.o a.o b.o c.o
main3:main3.o a.o b.o c.o
gcc -o main3 main3.o a.o b.o c.o


clean:
rm -f *.o main1 main2 main3


这样就生成了 三个 可执行文件main1 main2 main3
可见 伪目标也可以有依赖关系。
我们再可以运用变量把makefile写简单一些


objs=a.o b.o c.o


all:main1 main2 main3
.PHONY:all
main1:main1.o $(objs)
gcc -o main1 main1.o $(objs)
main2:main2.o $(objs)
gcc -o main2 main2.o $(objs)
main3:main3.o $(objs)
gcc -o main3 main3.o $(objs)


clean:
rm -f $(objs) main1 main2 main3
上面的例子是伪目标做为目标
再看一个清除的例子:这个例子说命了伪目标也可以作为依赖文件


objs=a.o b.o c.o


all:main1 main2 main3
.PHONY:all
main1:main1.o $(objs)
gcc -o main1 main1.o $(objs)
main2:main2.o $(objs)
gcc -o main2 main2.o $(objs)
main3:main3.o $(objs)
gcc -o main3 main3.o $(objs)


.PHONY:cleanall cleanobj cleanexe
cleanall:cleanobj cleanexe
rm -f $(objs) main?
cleanobj:
rm -f $(objs)
cleanexe:
rm -f main?


执行 make cleanall  rm -f $(objs) main?它被执行
执行 make cleanexe rm -f main?它被执行
执行 make cleanobj rm -f $(objs)它被执行
多目标:
静态模式:
看例子
CC = gcc
FLAGS = -g
LIBS = 
OBJS = main.o func1.o func2.o func3.o func4.o func5.o


all:$(OBJS)


$(OBJS):%.o:%.c
$(CC) -c $(FLAGS) $< -o $@ $(LIBS)
.PHONY:clean 
clean:
rm -f $(OBJS) exefile
这里的源文件是我们在上面第一个例子里的程序。
$(OBJS):%.o:%.c
$(CC) -c $(FLAGS) $< -o $@ $(LIBS)
多目标$(OBJS) 目标集是%.o 就是点o 结尾的文件 依赖目标集是:%.c 就是点c文件。 $< 表示 所有的 依赖目标集中的的点c 文件   $@ 表示所有的 目标集中的 点o文件。
展开后相当于:
gcc -c -g func1.c -o func1.o 
gcc -c -g func2.c -o func2.o 
gcc -c -g func3.c -o func3.o 
gcc -c -g func4.c -o func4.o 
gcc -c -g func5.c -o func5.o
这也是make后的结果


自动生成依赖关系:
我们可以用 这样的命令来查看每一个源文件的都包含了哪几个文件
Gcc –M main.c
执行结结果:
[yanggentao@wkp duomobiao]$ gcc -M main.c   
main.o: main.c head.h /usr/include/stdio.h /usr/include/features.h \
 /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
 /usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
 /usr/lib/gcc/i586-redhat-linux/4.4.0/include/stddef.h \
 /usr/include/bits/types.h /usr/include/bits/typesizes.h \
 /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
 /usr/lib/gcc/i586-redhat-linux/4.4.0/include/stdarg.h \
 /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h \
 /usr/include/stdlib.h /usr/include/sys/types.h /usr/include/time.h \
 /usr/include/endian.h /usr/include/bits/endian.h \
 /usr/include/bits/byteswap.h /usr/include/sys/select.h \
 /usr/include/bits/select.h /usr/include/bits/sigset.h \
 /usr/include/bits/time.h /usr/include/sys/sysmacros.h \
 /usr/include/bits/pthreadtypes.h /usr/include/alloca.h \
 /usr/include/string.h /usr/include/xlocale.h
[yanggentao@wkp duomobiao]$
执行完这个gcc –M main.c 后 它把所有的main.c 这个文件的包含文件都给打了出来,包括了库文件,我们用-MM两个可以不包含 这些库。
Gcc – MM main.c 
执行结果:
[yanggentao@wkp duomobiao]$ gcc -MM main.c  
main.o: main.c head.h
[yanggentao@wkp duomobiao]$
Main.c 文件只包含head.h 这个头文件
在命令行前 加一个@ 字符 他就不会打印执行的命令了
@$(CC) -c $(FLAGS) $< -o $@ $(LIBS)
这样子结果什么都没有打印,但命令行还是会执行,只是不打印了而已
而如果是 make –n 那么 make 只显示执行的命令而 不会去执行了
命令执行:
如果你想要上一个命令的执行结果影响下一个命令的执行,那么这两个命令的就要卸载一行上用分号隔开,而不能写在两行上。
例子:
exec:
cd ~;pwd
exec:
cd ~
pwd
结果是不一样的。
命令出错:
如果命令出错了就有可能影响到后面的命令。如果这样
在命令前面加上-,那么命令出了错,他也会执行下面的命令的
Clean:
-rm –f *.o
还有一个全局的办法就是在make 上加上 –I 就像了
Make –k 的意思就是 如果某个规则出错了,就终止这个规则,但要继续执行下一个规则;
嵌套执行make
嵌套执行make 是适用于大工程里的。我们会把不同的模块或不同功能的程序放在不同的目录中,而在每一个目录下都会有一个makefile文件,这个技术对于我们木块编译和分段编译有着非常大的意义;




:= 和 =  的区别是 = 是可以不用先声明才使用的 而:= 必须要用前面的定义过的
?= 的作用是  a ?= b  如果a 定义了已经,那么 这条语句什么都不会做,如果没有定义那么 a 的值就是b


变量的高级用法:
 foo:=a.o b.o c.o
bar:=$(foo:.o=.c)
上面的意思是:bar 等于 a.c b.c c.c 就是在foo这个变量中的 以.o结尾的字串用.c结尾的代替:
也可以这样写了:
Bar:=$(foo:%.o=%.c)
+= 的意思是 追加变量的值:
例如:
Source = a.c b.c c.c d.c
Source += e.c
那么source 就等于  a.c b.c c.c d.c e.c
e.c 就追加上了;



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a746742897

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值