makefile

 

Makefile是什么

关于程序的编译和链接

  一般来说,无论是C还是C++,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile),一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

  编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。

  链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来 链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

简单的说,Makefile是告诉用户程序该怎么编译,用户可以简单使用make命令就能编译一个软件,极大地提升了编程效率。

make命令默认使用名称为Makefile或makefile的文件作为编译规则,若想指定makefile文件,使用make -f file

Makefile规则

Makefile简单的规则为:

1
2
目标 : 需要的条件  (注意冒号两边有空格)
命令 (注意前面用tab键开头)
  1. 目标可以是一个或多个,可以是Object File,也可以是执行文件,甚至可以是一个标签。
  2. 需要的条件就是生成目标所需要的文件或目标
  3. 命令就是生成目标所需要执行的脚本

  总结一下,就是说一条makefile规则规定了编译的依赖关系,也就是目标文件依赖于条件,生成规则用命令来描述。在编译时,如果需要的条件的文件比目标更新的话,就会执行生成命令来更新目标。

万能Makefile模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#指定编译器
CC = g++

#定义rm命令
RM = /bin/rm

#定义echo命令,用于打印信息
ECHO = /bin/echo

#编译选项,定义了HAVE_DEBUG变量,用于调试
DEFINES = -DHAVE_DEBUG

#编译选项,
#-g是生成debug版本,-O2代码优化,
#-Wall提示所用waring,可根据实际需要来选择
CPPFLAGS = -g -O2 -Wall

#把include 加入到搜索头文件的路径列表中
INCLUDES = -Iinclude

#-lcrypto进行链接时搜索名为crypto的库,
#-lssl 进行链接时搜索名为ssl的库
#-Llib 把lib加入到搜索库文件的路径列表中
LDFLAGS = -lcrypto -lssl

#定义生成可执行文件名
MAIN_EXE = hello

#定义变量,定义all操作的条件,可包含多个要生成的可执行文件
#例如EXECUTABLES = main1 main2,这样执行all操作就会分别生成main1 和 main2
EXECUTABLES = $(MAIN_EXE)

#指定源文件,
#wildcard是通配符,搜索./src目录下所有以.cpp结尾的文件,
#生成一个以空格间隔的文件名列表,并赋值给SOURCES.
#当前目录文件只有文件名, 子目录下的文件名包含路径信息,比如./src/bar.cpp。
SOURCES = $(wildcard src/*.cpp)

#指定要生成的*.o文件,
#patsubst是pattern substitute的缩写,匹配替代的意思。
#这句是在SOURCES中找到所有.cpp 结尾的文件,然后把所有的.cpp换成.o。
OBJECTS = $(patsubst %.cpp, %.o, $(SOURCES))

#PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字。
#有两种理由需要使用PHONY 目标:避免和同名文件冲突,改善性能。
#如果编写一个规则,并不产生目标文件,则其命令在每次make 该目标时都执行。
.PHONY: all clean

#目标为all需要条件,$(EXECUTABLES)
#由于.PHONY中有all,可解决执行文件生成的依赖关系
all: $(EXECUTABLES)

#生成$(MAIN_EXE),需要条件$(OBJECTS)
#使用$(CC)来生成$(MAIN_EXE),具体g++用法请自行查阅
#$@表示目标
#$^所有依赖目标的集合,以空格分隔。
#如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
#该命令在本makefile中被扩展为,假设$(OBJECTS) = src/bar.o
#hello: src/bar.o
# g++ -o hello bar.o -lcrypto -lssl
$(MAIN_EXE): $(OBJECTS)
$(CC) -o $@ $^ $(LDFLAGS)

#生成$(MAIN_EXE),需要先生成$(OBJECTS)
#目标是生成src/%.o, 需要src/%.cpp,其中%为通配符
#$< 表示依赖目标中第一个目标的名字。
#如果依赖目标是以模式(%)定义的,那么"$<"将是符合模式的一系列文件集。(注:是一个一个取出来的)
#命令被扩展为
#src/bar.o: src/bar.cpp
# g++ -o src/bar.o -c src/bar.cpp -DHAVE_DEBUG -g -O2 -Wall -Iinclude
src/%.o: src/%.cpp
$(CC) -o $@ -c $< $(DEFINES) $(CPPFLAGS) $(INCLUDES)

#执行clean操作
clean:
- $(RM) -rf $(OBJECTS) $(MAIN_EXE)

.PHONY的用法详细可参考.PHONY的用法

\$@等都是Makefile中的特殊变量,更多特殊变量请参考:makefile中的特殊变量

测试

新建一个项目,要实现的功能是对字符串”1234“计算MD5值。

include/common.h

1
2
3
4
5
6
7
#ifndef _COMMON_H
#define _COMMON_H
#include <string>

std::string get_string_MD5(const std::string str);

#endif

src/common.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "common.h"
#include <openssl/md5.h>
#include <string.h>
#include <stdio.h>
std::string get_string_MD5(const std::string str)
{
MD5_CTX ctx;
char *data = (char *)str.c_str();
unsigned char md[16]={0};
char buf[33]={0};
char tmp[3]={0};
int i;

MD5_Init(&ctx);
MD5_Update(&ctx,data,strlen(data));
MD5_Final(md,&ctx);

for(i=0;i<16;i++)
{
sprintf(tmp,"%X",md[i]);
strcat(buf,tmp);
}
return std::string(buf);
}

src/main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include "common.h"

using namespace std;

int main()
{
string str = "1234";
string after = get_string_MD5(str);
cout<<"Before MD5: "<<str<<endl;
cout<<"After MD5: "<<after<<endl;
return 0;
}

示例需要安装openssl库;在目录下运行make,然后运行./hello

整个示例的下载地址:md5.zip