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键开头)
|
- 目标可以是一个或多个,可以是Object File,也可以是执行文件,甚至可以是一个标签。
- 需要的条件就是生成目标所需要的文件或目标
- 命令就是生成目标所需要执行的脚本
总结一下,就是说一条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