一、前言

Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。

然而make的命令"博客精深",对于初学者来说,真是望而生畏,这篇文章不是make详解,只是讲解实用makefile的编写和使用。

在linux上,如果用gcc一个个编译源码,实在很繁琐,尤其是随着源代码的增加,这种繁琐更是明显,很多人,包括我,其实要求很简单,只要把头文件和cpp或者c文件放在当前目录或者其他目录,直接一个命令就可以编译了,但是make的命令太多了,相当打击初学者的信心。

常用的make指令其实不多,够用就行了,接下来开始讲解实用makefile的编写,看完之后大家可以直接下载make工程直接一个make命令完成编译。

本文相关环境:redhat 5.5 + g++ version 4.1.2 + GNU Make 3.81

g++可以支持cpp文件编译,gcc编译cpp文件在链接时会出现点问题,所以这里统一使用g++。

二、Makefile的规则

target ... : prerequisites ...command......

comman如果和target不是同一行,需要在第二行键入\t再键入command.

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。

prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

三、使用make一个个编译源代码

假设我们写一个简单的程序,源码位于/usr/make/main.cpp,代码如下:

//main.cpp
#include <stdio.h>
int main(int argc, char** argv) {printf("app startup\n");printf("app stop\n");return 0;
}

make的最大好处是自动化编译,于是我们新建/usr/make/makefile,makefile文件内容如下:

main:   main.og++ main.o -o main
main.o: main.cppg++ -c main.cpp -o main.o
clean:rm -rf *.o main

保存之后,直接执行make,就可以生成main.o目标文件,main可执行文件了。

然而我们这个程序实在单薄了点,于是我们需要加入一个App类,控制整个程序的启动和关闭周期。

新建/usr/make/app.h文件,代码如下:

#ifndef APP_H
#define APP_H class App{public:static App& getInstance();bool start();bool shutdown();private:App();App(const App&);App& operator=(const App&);bool m_stopped;
};#endif

新建/usr/make/app.cpp文件,代码如下:

#include "app.h"#include <stdio.h>
#include <unistd.h>
App& App::getInstance() {static App app;return app;
}App::App() {m_stopped = false;
}bool App::start() {printf("app startup\n");while (!m_stopped) {printf("app run\n");sleep(5);}return true;
}bool App::shutdown() {if (m_stopped == false) {m_stopped = true; }return true;
}

修改/usr/make/main.cpp文件,代码如下:

//main.cpp
#include <stdio.h>#include "app.h"int main(int argc, char** argv) {App& app = App::getInstance();if(!app.start()) {printf("app start fail\n");}app.shutdown();return 0;
}

修改/usr/make/makefile,内容如下:

main:    main.o app.og++ main.o app.o -o main
main.o:    main.cppg++ -c main.cpp -o main.o
app.o:    app.cppg++ -c app.cpp -o app.o
clean:rm -rf *.o main

执行make,./main,发现后台会每隔5秒打印出"app run",只能ctrl+c结束.

以后我们修改了main.cpp,app.cpp,app.h文件后,我们只要输入make,就可以自动编译了,不必每次一个个g++命令去进行繁杂的编译过程。

这种方式,以后每添加一个源码文件,我们就要手工修改makefile的内容,添加依赖和命令,仍然显得不自动。

makeprojectv1下载

四、使用make自动推导编译

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

我们的是新的makefile又出炉了:

CPP_SOURCES = $(wildcard *.cpp)
CPP_OBJS = $(patsubst %.cpp, %.o, $(CPP_SOURCES))default:compile$(CPP_OBJS):%.o:%.cppg++ -c $< -o $@compile: $(CPP_OBJS)g++ $^ -o mainclean:rm -f $(CPP_OBJS)rm -f main
下面来一步步解析这个makefie的语法.
初始化:
wildcard函数功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔,本例是产生一个所有以 '.cpp' 结尾的文件的列表,然后存入变量 CPP_SOURCES.
patsubst函数是匹配替换的函数,有三个参数,第一个是一个需要匹配的式样,第二个表示用什么来替换它,第三个是一个需要被处理的由空格分隔的字列,本例是把
CPP_SOURCES的后缀为cpp文件列表,替换成后缀为o的文件列表。
生成目标文件
$(CPP_OBJS):%.o:%.cppg++ -c $< -o $@

上面的例子中,指明了我们的目标从$(CPP_OBJS)中获取,"%.o"表明要所有以".o"结尾的目标,也就是"main.o app.o",也就是变量$(CPP_OBJS)集合的模式.
而依赖模式"%.cpp"则取模式"%.o"的"%",也就是"main app",再加上后缀"cpp".于是,我们的依赖目标就是"main.cpp app.cpp"。
而命令中的"$<"和"$@"则是自动化变量,"$<"表示所有的依赖目标集(也就是"main.cpp app.cpp"),"$@"表示目标集(也就是"main.o cpp.o")。

展开之后是:

main.o: main.cppg++ -c main.cpp -o main.o
main.o: app.cppg++ -c app.cpp -o app.o

链接

上述的default目标是是makefile的第一个目标,依赖compile,compile文件不存在,也不会出现,所以会执行compile目标。

default和compile只是makefile一个普通的规则而已,没有特殊之处。

compile: $(CPP_OBJS)g++ $^ -o main

"$<"表示所有的依赖目标集,表示main.o app.o

展开之后是

compile: main.o app.og++ main.o app.o -o main

这种方式,当我们在当前目录添加cpp文件,不用再改makefile,只要键入命令make就可以完成整个编译过程,这个时候我们可以感受到make的自动化编译的方便。

makeprojectv2下载

五、实用makefile的完善

虽然我们已经使用了makefile的自动推导,但是这个makefile还有有些不足:

1.只能支持cpp文件,不支持c文件

2.生成的目标文件全放在当前目录,造成当前目录混乱

3.h文件和cpp文件或者c文件没有分离,造成目录里的源码文件2倍爆炸,而且也不支持多目录

4.不支持第三方的库文件,包括include文件和lib文件。

于是,有一个完善的makefile如下:

TARGET = main
OBJ_PATH = objsCC = g++
CFLAGS = -Wall -Werror -g
LINKFLAGS =#INCLUDES = -I include/myinclude -I include/otherinclude1 -I include/otherinclude2
INCLUDES = -I include
#SRCDIR =src/mysrcdir src/othersrc1 src/othersrc2
SRCDIR = src
#LIBS = -Llib -lcurl -Llib -lmysqlclient -Llib -llog4cpp
LIBS =C_SRCDIR = $(SRCDIR)
C_SOURCES = $(foreach d,$(C_SRCDIR),$(wildcard $(d)/*.c) )
C_OBJS = $(patsubst %.c, $(OBJ_PATH)/%.o, $(C_SOURCES))CPP_SRCDIR = $(SRCDIR)
CPP_SOURCES = $(foreach d,$(CPP_SRCDIR),$(wildcard $(d)/*.cpp) )
CPP_OBJS = $(patsubst %.cpp, $(OBJ_PATH)/%.o, $(CPP_SOURCES))default:init compile$(C_OBJS):$(OBJ_PATH)/%.o:%.c$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@$(CPP_OBJS):$(OBJ_PATH)/%.o:%.cpp$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@init:$(foreach d,$(SRCDIR), mkdir -p $(OBJ_PATH)/$(d);)compile:$(C_OBJS) $(CPP_OBJS)$(CC)  $^ -o $(TARGET) $(LINKFLAGS) $(LIBS)clean:rm -rf $(OBJ_PATH)rm -f $(TARGET)install: $(TARGET)cp $(TARGET) $(PREFIX_BIN)uninstall:rm -f $(PREFIX_BIN)/$(TARGET)rebuild: clean compile

这个makefile的功能是扫描src文件夹的c文件和cpp文件,进行编译,编译的命令是g++,编译选项是-Wall -Werror -g,表示尽可能打印出报错信息,有错误和警告就停止编译,并且生成调试信息。如果是生产环境,请把-g去掉。最后连接所有目标文件为main。如果所有的c文件和cpp文件没有main函数入口,链接会报错。

使用这个makefile,需要注意的几个变量是:

TARGET 表示最后生成的可执行文件的名字,默认生成的main可执行文件在当前工作目录。
OBJ_PATH  表示编译过程中产生的目标文件放在那个目录,默认生成放在在当前目录的objs目录下,无需手工mkdir,自动生成。
CC 默认是g++,如果是使用gcc的,请修改,但是gcc在编译cpp文件后,链接时会出现问题,请自行解决。
INCLUDES 配置头文件的目录,默认是include目录,如果放在当前目录,可以改为"INCLUDES = .",如果要配置多个include目录,可以按照注释,自行配置。
SRCDIR 配置c文件和cpp文件的目录,默认是src目录,如果喜欢放在当前目录,可以改为"SRCDIR= .",如果要配置多个源码目录,可以按照注释,自行配置。
LIBS 配置第三方的动态链接库和静态链接库,当程序使用了第三方库时,我们编译是要配置第三方的头文件目录,链接时就要配置链接的库文件,LIBS配置可以参考注释,如果不清楚,请看本人博客里的“gcc常用命令”文章

makeprojectv3下载

实用make最佳实践相关推荐

  1. 最佳实践 ADO.NET实用经验无保留曝光

    ADO.NET作为微软最新的数据访问技术,已经在企业开发中得到了广泛的应用.对于一线的开发人员来说,掌握基本的概念和技术之后,提高应用水平和解决实际问题的最有效手段,莫过于相互交流彼此的最佳时间经验经 ...

  2. UI效率实用素材|WEB数据可视化最佳实践

    数据可视化已经迅速成为在web上传播信息的标准.它被广泛应用于从商业智能到新闻业的各个行业,以帮助我们理解和交流数据中的洞察力. 我们的大脑习惯于处理可视化的信息,这使得我们更容易理解图表中的数据,而 ...

  3. CSS 布局:40个教程、技巧、例子和最佳实践

    前言: 布局是WEB开发一个重要的课题,进入XHTML/CSS后,使用TABLE布局的方式逐渐淡出,CSS布局以众多优点成为主流,本文将介绍40个基于CSS的web布局的资源和教程.文章的出处在htt ...

  4. 八大深度学习最佳实践

    翻译 | AI科技大本营 参与 | 刘畅 [AI 科技大本营导读] 2017年,许多的人工智能算法得到了实践和应用.名博Hack Noon作者 Brian Muhia 认为想要玩转人工智能,不仅要拥有 ...

  5. Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:六点半起床 juejin.im/post/685654110 ...

  6. RESTful API 设计最佳实践

    2019独角兽企业重金招聘Python工程师标准>>> 背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中"RESTful API "简写为 ...

  7. oracle数据库如何写翻页_ORACLE数据库分页查询/翻页 最佳实践

    ORACLE数据库分页查询/翻页 最佳实践 一.示例数据: Select Count(*) From dba_objects ; ----------------------------------- ...

  8. Nature综述:工程微生物组的通用原则和最佳实践

    文章目录 NRM:工程微生物组的通用原则和最佳实践 摘要Abstract 专业词汇 正文 Main 设计微生物组(Designing microbiomes) 图1 |微生物组工程的设计-构建-测试- ...

  9. 独家 | 提升API设计技能的22个最佳实践(附链接)

    作者:Mohammad Faisal翻译:张一然校对:和中华本文约2000字,建议阅读7分钟本文介绍了有关设计REST api的一些实用建议. 你是否曾对处处都像猜谜游戏一样的糟糕API感到生气, 好 ...

最新文章

  1. 石墨文档技术总监:敏捷思想在产品周期的延伸
  2. Ajax+asp.net实现用户登陆 转自http://www.shangxueba.com/jingyan/2933319.html
  3. C语言实现常用数据结构——队列
  4. IDF实验室-图片里的英语
  5. ios framework 找不到.h_找不到好看的壁纸?上万张「高清壁纸」,都在iOS捷径里...
  6. c# asp.net mvc 开发的正方教务助手(一)
  7. oracle中pga指什么,oracle学习SGA跟PGA理解
  8. 《代码敲不队》第三次作业:团队项目的原型设计
  9. 组态软件开发(zz)
  10. 二分查找(Java实现)
  11. 软件构建中的设计(一)
  12. Iperf 网络性能测试
  13. docker制作python项目镜像
  14. 网络信息安全知识框架
  15. 腾讯音乐评论审核、分类与排序算法技术
  16. SQL-DAY 7(SQL查询语句的应用案例:汽车新销售)
  17. 养成不断学习的好习惯_如何使用“小习惯”养成一致的学习习惯。
  18. 用 Python 创作酷炫的几何图形
  19. diyUpload - jQuery多张图片批量上传插件
  20. 简述Thread的interrupt()、interrupted()及isInterrupted()的区别

热门文章

  1. (八十二)利用苹果服务器获取导航信息和绘制路径
  2. 使用nginx进行负载均衡
  3. 位置高度ios 开发中跟绘图相关的CGFloat,CGPoint,CGSize,CGRect,CGRectZero
  4. 意法半导体STM32 ARM Cortex 32位微控制器
  5. 八句经典座右铭必有一句适合你
  6. 受益终生的世界顶级八大思维
  7. qt的输出中文,数字到表格
  8. html输入框颜色属性,css 修改input输入框属性
  9. android 发送前台广播,使用IntentService与BroadcastReceiver实现后台服务(Android7.0可用)...
  10. linux按顺序运行命令,linux – 安排cron作业打开终端并按顺序运行命令