学习自《跟我一起写Makefile》,《linux网络编程》
生成可执行文件的过程分为编译,汇编,链接等阶段,make工程管理器可以根据文件的时间戳发现更新后的文件有选择的编译,通过读入规则文件来执行编译工作。在整个过程中,make工程管理器调用了gcc编译器。
编译和链接
Uinx下编译产生的.o文件是中间代码文件,object file. 只要源文件代码正确,那么我们就可以编译出相应的object file。到了链接阶段(链接函数和全局变量),链接器寻找object file,如果中间代码文件太多,则可以给其打包,生成Archive File,即.a文件,链接后生成可执行文件。
编译器只检查语法和函数、变量的声明,链接器则是寻找函数的实现。
makefile的规则:
target ... : prerequisites ...
command
...
...
prerequisites(依赖文件)中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
clean不是一个文件,它只不过是一个动作名字。要执行其后的命令(不仅用于clean,其他lable同样适用),就要在make命令后明显得指出这个lable的名字。我们可以在一个makefile中定义程序的打包,程序的备份,等等。
注意:第一行的target是总的目标,后面的都是进行依赖处理。
gcc的-c和-o
gcc -c test.c #编译test.c文件得到test.o
gcc -c test.c -o test.o #同上
gcc -o test test.o #链接test.o可执行文件
gcc -o test test.c #将test.c编译并连接成可执行文件
gcc test.c -o test #同上
make工作方式
输入make命令后:
1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2. 如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件。
3. 如果target文件不存在,或是所依赖的后面的 .o 文件的文件修改时间要比target文件新,那么,他就会执行后面所定义的命令来生成target文件。
4. 如果target所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
make只管文件的依赖性, 如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错
make变量
3 个预定义变量介绍:
1. $@ 表示要生成的目标
2. $^ 表示全部的依赖文件
3. $< 表示第一个依赖文件
4个参数:
1. -o 指定目标文件
gcc sources/main.c -o bin/main
2. -c 编译的时候只生产目标文件不链接
gcc -c sources/main.c -o obj/main.o
3. -I 主要指定头文件的搜索路径
gcc -I headers -c main.c -o main.o
4. -l 指定静态库
gcc -lpthread
使用$(var)来代替变量var的值,用“$$”来表示$。
变量可以嵌套$($(x))
可以使用“+=”操作符给变量追加值。
更多变量说明,见:http://blog.csdn.net/thearcticocean/article/details/52729873 的makefile部分。
makefile的注释:
单行: 行首#
多行: 行首# 行尾\
比如:
#PWD := $(shell pwd)
#INC := $(PWD)/inc\
DEBUG := -g\
MAIN_OBJ := $(PWD)/*.o\
ADD_OBJ := $(PWD)/add/*.o\
SUB_OBJ := $(PWD)/sub/*.o\
OBJ := $(SUB_OBJ) $(ADD_OBJ) $(MAIN_OBJ)\
\
main: $(OBJ)\
cc -o main $(OBJ) \
\
$(MAIN_OBJ) : $(PWD)/*.c $(INC)/*.h\
$(ADD_OBJ) : $(PWD)/add/*.c\
$SUB_OBJ) : $(PWD)/sub/*.c\
make自动推导
make的“隐晦规则”: 只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,并推导出相关的命令。
关于tab缩进
在编写makefile的过程中,某一行出现红色那就要小心了,因为出现了错误。
编写makefile相关的vimrc配置:
set noexpandtab
set softtabstop=4
来自《linux网络编程》的一个加法、减法的工程例子:
文件目录稍作改动。
相关代码:
add_float.c
float add_float(float a, float b)
{
return a+b;
}
add_int.c
int add_int(int a, int b)
{
return a+b;
}
sub_float.c
float sub_float(float a, float b)
{
return a-b;
}
sub_int.c:
int sub_int(int a, int b)
{
return a-b;
}
add.h
#ifndef __ADD_H__
#define __ADD_H__
extern int add_int(int a, int b);
extern float add_float(float a, float b);
#endif /*__ADD_H__*/
sub.h
#ifndef __SUB_H__
#define __SUB_H__
extern float sub_float(float a, float b);
extern int sub_int(int a, int b);
#endif /*__SUB_H__*/
main.c
#include <stdio.h>
#include "./inc/add.h"
#include "./inc/sub.h"
int main(void)
{
int input = 0;
int a = 10, b = 12;
float x= 1.23456,y = 9.87654321;
printf("int a+b IS:%d %d\n",a+b,add_int(a,b));
printf("int a-b IS:%d %d\n",a-b,sub_int(a,b));
printf("float x+y IS:%f %f\n",x+y,add_float(x,y));
printf("float x-y IS:%f %f\n",x-y,sub_float(x,y));
return 0;
}
现在,写一个简单的makefile,使得产生的.o文件都在mkfl下,然后链接即可。
PWD := $(shell pwd)
INC := $(PWD)/inc
ADD := $(PWD)/add
SUB := $(PWD)/sub
DEBUG := -g
OBJ := $(PWD)/*.o
.PHONY: all
all: lib
gcc -o main $(OBJ)
.PHONY: lib
lib:
cc -c $(ADD)/*.c
cc -c $(SUB)/*.c
cc -c $(PWD)/main.c $(INC)/*.h
.PHONY:clean
clean:
#make clean -C $(PWD)
rm -f main *.o
清洁文件
删除命令:
-rm -f main *.o
在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。clean从来都是放在文件的最后
makefile的规则
显式规则:各种依赖关系和命令
隐式规则:利用make的自动推导
特定的makefile文件
例如写了一个makefile_special
那么可以提示make寻找并解释它 make -f makefile_special
makefile的嵌套
include <filename>
可以在本makefile中引入其他makefile。
在include前面加上’-‘也能暂时屏蔽掉错误信息,继续执行后面的东西。
通配符的展开和替换
如果想要通配符在变量中展开,那么我们需要关键字 wildcard(拓展通配符).
确定所有的.o文件 object := $(wildcard *.o)
如果想要进行通配集合的替换,那么需要关键字patsubst
确定所有.c文件对应的.o文件 object := $(pubsubst %.c, %.o, $(wildcard *.c))
[%为makefile的规则通配符]
文件搜索
vpath <pattern> <directories>
关键字vpath用于搜索符合pattern模式的文件,且在指定的路径下进行搜索。
比如 vpath %.c ./src
在src文件夹中查找所有的.c文件。
如果需要指定多个路径,那么我们可以一一列出。
vpath %.c dir1
vpath %.c dir2
vpath %.c dir3
那么make将会先搜索dir1, 让然后是dir2, 最后是dir3
伪目标
使用.PHONY
来指定伪目标,它不是文件,仅仅是一个标签。我们可以显示的指定来进行make。
比如上面加减例子中的makefile,如果使用命令make lib
,那么我们将生成所有的.o文件,但是可执行文件main是不会产生的。
利用伪目标生成多个可执行文件:
all : enjoy hello love
.PHONY: all
enjoy: enjoy.o
cc enjoy.c -o enjoy
hello: hello.o
cc hello.c -o hello
love: love.o
cc love.c -o love
.PHONY: clean
clean:
rm -f *.o
rm -f enjoy hello love
第一行指明终极目标,后面又指出这个all是个伪目标,所以不会产生all文件。但是其依赖的三个文件被创建出来了。
当然,你也可以写一个shell脚本,来几行gcc -o ...
,这样也行。
自动搜索依赖头文件
gcc -M main.c #这会将库文件也打出来
gcc -MM main.c #仅输出include的头文件
还是以上面的加减例子做说明
cc -MM main.c
main.o: main.c inc/add.h inc/sub.h
[.d]文件中就存放对应[.c]文件的依赖关系