一、Makefile简介

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

二、简单编写一个Makefile模板

当编译少量的源文件时,可以用以下两种方法编译

1、直接用一条命令编译

  gcc -o test 1.c 2.c

编译过程说明:

执行gcc -o test 1.c 2.c时,

对于1.c:预编译、编译、汇编、链接

对于1.c:预编译、编译、汇编、链接

优点:命令简单,易操作

缺点:如果文件很多,即使修改了一个文件,但是所有的文件都要重新"预处理、编译、汇编",效率很低

2、通过Makefile建立编译规则

一个的Makefile文件包含一系列的规则(想了解更多规则可参考《GNU make手册》),其格式如下:

目标 : 依赖1 依赖2 ... 依赖n

  命令

注意:命令的前边必须是一个Tab

命令执行的条件:

(1)"依赖"文件比"目标"的时间新

(2)没有"目标"这个文件

三、代码实践

功能说明:当任何一个文件发生改变时,其所有依赖文件和自己都会被重新编译,而其他与此没有关联的文件则不会被编译

源文件如下:

<1>文件1.c

#include <stdio.h>
#include "1.h"
int main(char argc, char *argv[])
{
printf("This is 1.c\n");
func();
printf("AA = %d\n", AA); return ;
}

<2>文件2.c

#include <stdio.h>
void func(void)
{
printf("This is 2.c\n");
}

<3>文件1.h

#define AA     2

1、第1个Makefile

 target:.c .c .h
gcc -o target .c .c

解析:
第一次编译(没有目标生成)或依赖发生变化(依赖比目标新)就执行下面的命令这样的缺点就是,只要其中的一个依赖发生变化,所有的文件都要重新被编译一遍,这样效率极低

2、第2个Makefile

target:.o .o
gcc -o target .o .o
#-c表示只编译不链接(只进行预编译、编译、汇编)
.o : .c
gcc -c -o .o .c .o : .c
gcc -c -o .o .c

解析:

解析:当执行make命令时,想要生成第一个目标target,而target依赖于1.o、2.o,但是当前目录下并没有1.o、2.o,它会用1.o往下查找到1.o依赖于1.c,同时判断(1.c是否比1.o新或1.o是否存在)是否执行gcc -c -o .1.o 1.c,我们这里满足第二个条件所以此命令会被执行,分析1.o也是如此

3、第3个Makefile(效果同第2个Makefile)

target:.o .o
gcc -o target .o .o %.o : %.c
gcc -c -o $@ $<

解析:

%.o : %.c,
gcc -c -o $@ $<
等同于
1.o : 1.c
gcc -c -o 1.o 1.c
2.o : 2.c
gcc -c -o 2.o 2.c
%为通配符,%.o,表示所有的.o文件%.c,表示所有的.c文件,
$@表示目标(对应%.o),$<表示第一个依赖(对应%.c)

测试:
(1)第一次编译,则所有的文件都会被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# ls
1.c 1.h 2.c Makefile
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 1.o 1.c
gcc -c -o 2.o 2.c
gcc -o target 1.o 2.o
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# ls
1.c 1.h 1.o 2.c 2.o Makefile target

(2)只修改1.c,则所有依赖1.c的文件都会重新被编译,而2.c不会重新被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 1.c
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 1.o 1.c
gcc -o target 1.o 2.o

(3)只修改2.c,则所有依赖2.c的文件都会重新被编译,而1.c不会重新被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 2.c
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 2.o 2.c
gcc -o target 1.o 2.o

(4)但是修改1.h就不行了,不能检测到1.h的更新,第4个Makefile中继续修改
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 1.h
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
make: `target' is up to date.

4、第4个Makefile

target:.o .o
gcc -o target .o .o
#添加此行,当修改1.h时,所有依赖1.h的文件都会重新编译
.o : .c .h %.o : %.c
gcc -c -o $@ $<

解析:
(1)1.o依赖于1.c,同样也依赖于1.h,故添加1.o : 1.c 1.h
(2)make时就会检测1.c所包含的1.h是否发生改变,若发生改变,则重新编译
(3)但存在一个问题,当有上万个.c文件时,要查看它所包含的.h文件是否发生改变,这样操作就不行了,那么是否有什么自动的规则,来生成依赖文件,第5个Makefile中继续修改

测试:
root@ubuntu:/mnt/hgfs/winshare/mak/4_makefile# make
gcc -c -o 1.o 1.c
gcc -o target 1.o 2.o

5、第5个Makefile

利用-Wp,-MD,1.d自动生成依赖存到1.d中,1.o的所有依赖如下
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# gcc -c -o 1.o 1.c -Wp,-MD,1.d
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# ls
1.c 1.d 1.h 1.o 2.c Makefile
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# vi 1.d
1.o: 1.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h 1.h

target:.o .o
gcc -o target .o .o %.o : %.c
gcc -c -Wp,-MD,$@.d -o $@ $< clean:
rm *.o target distclean:
rm *.o target *.d

(1)-Wp,-MD,$@.d表示编译时自动生成1.o.d、2.o.d,所有关于1.o、2.o的依赖
都保存在1.o.d、2.o.d
(2)clean,distclean表示伪目标,make clean 时,则执行rm *.o target,
make distclean也如此

6、第6个Makefile

objs := .o .o

target:$(objs)
gcc -o target $^
$(warning $(objs))
# ..o.d ..o.d
dep_files := $(foreach f,$(objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
$(warning $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif %.o : %.c
gcc -c -Wp,-MD,.$@.d -o $@ $< clean:
rm *.o target distclean:
rm -f *.o target .*.d

解析:
(1)定义一个立即变量objs来表示target的依赖
(2)$(foreach f,$(objs),.$(f).d)的分析:
foreach  含义是每一个,是一个函数,类似于for循环
     f   代表$(objs)中的一个成员变量1.o或2.o,
.$(f).d    把$(objs)中的每个成员都加上前缀.和后缀.d,如.1.o.d和.2.o.d
(3)$(wildcard $(dep_files))表示取出$(dep_files)中的成员变量
(4)ifneq ($(dep_files),)判断$(dep_files)是否非空,非空条件成立
(5)至此,所有.h类型的依赖被包含进去

测试:
单独修改1.h编译成功,修改AA的值为3
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# vi 1.h
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# make
.1.o.d .2.o.d
gcc -c -Wp,-MD,.1.o.d -o 1.o 1.c
gcc -o target 1.o 2.o
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# la
1.c 1.h 1.o .1.o.d 2.c 2.o .2.o.d Makefile target
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# ./target
This is 1.c
This is 2.c
AA = 3

1、编写一个简单Makefile模板的更多相关文章

  1. 编写一个简单的内核驱动模块时报错 “/lib/modules/3.13.0-32-generic/bulid: 没有那个文件或目录。 停止。”

    编写一个简单的内核驱动模块 static int hello_init() { printk(“hello,I am in kernel now\n”); ; } void addfunc(int a ...

  2. 使用CEF(二)— 基于VS2019编写一个简单CEF样例

    使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...

  3. 编写一个简单的C++程序

    编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...

  4. 使用Java编写一个简单的Web的监控系统cpu利用率,cpu温度,总内存大小

    原文:http://www.jb51.net/article/75002.htm 这篇文章主要介绍了使用Java编写一个简单的Web的监控系统的例子,并且将重要信息转为XML通过网页前端显示,非常之实 ...

  5. 一个简单的模板引(han)擎(shu)

    自制一个简单的模板引(han)擎(shu) 原理 说大了 实际上是模板函数 原理呢就是简单的字符串替换 第一版 var data = { username: 'Muhha' } str = '< ...

  6. 编写一个简单的Web Server

    编写一个简单的Web Server其实是轻而易举的.如果我们只是想托管一些HTML页面,我们可以这么实现: 在VS2013中创建一个C# 控制台程序 编写一个字符串扩展方法类,主要用于在URL中截取文 ...

  7. javascript编写一个简单的编译器(理解抽象语法树AST)

    javascript编写一个简单的编译器(理解抽象语法树AST) 编译器 是一种接收一段代码,然后把它转成一些其他一种机制.我们现在来做一个在一张纸上画出一条线,那么我们画出一条线需要定义的条件如下: ...

  8. Java入门篇(一)——如何编写一个简单的Java程序

    最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...

  9. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...

随机推荐

  1. TP5 生成二维码

    首先下载这个类:http://phpqrcode.sourceforge.net/ 把下载的文件放到vendor下面 public function getWchatQrcode($users_id= ...

  2. 关于mybatis配置文件mapper传int值的问题

    1.首先看mapper代码,这是个更新语句. <set> <if test="sendmode!='' && sendmode!=null"> ...

  3. 移动终端设备ID

    转自:https://wetest.qq.com/lab/view/116.html 一.前言 对于移动端产品的常规统计分析和运营推广,渠道结算来说,能精准的识别区分并且跟踪一台终端设备(一个终端用户 ...

  4. 实现鼠标悬停,div勾画div边框的动画

    鼠标悬浮,边框div边框的动画样式,效果图如下: 首先定义div及其样式: <style> .show { width:300px; height:200px; border:1px so ...

  5. 08_java基础知识——方法重载

    一.自变量顺序不同 package com.huawei.test.java04; /** * This is Description * * @author 王明飞 * @date 2018/08/ ...

  6. vue项目中全局配置变量

    在项目中api管理需要用到全局变量,创建全局变量的方式也有很多. 1.通过export default const BASEURL = "http://localhost:3333/&quo ...

  7. git教程:管理修改

    转载:管理修改 现在,假定你已经完全掌握了暂存区的概念.下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件. 你会问,什么是修改?比如你新增了一行 ...

  8. XMind 8 pro update 7激活方法

    激活过程 0.下载XMindCracker.(自行百度下载)1.断网,使用修改hosts方法,在最后一行添加0.0.0.0 www.xmind.net2.将XMindCrack.jar拷贝到XMind ...

  9. Linux 学习之路 --------ip地址虚拟网络

    // ifconfig 查看IP地址 网络信息   我的IP  39.161.136.25 ①     为网卡临时配置IP地址 ifconfig eth0 39.161.136.5 (netmask ...

  10. vue 基础重要组件 模板指令 事件绑定

    组件:data methods watch new vue({ data:{ a:1, b:[] }, methods:{ dosomething:function(){ this.a++; } }, ...