一、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. L364 Should Your Resume Be One Page or Two?

    Should Your Resume Be One Page or Two? Conventional wisdom suggests that you should keep it short: A ...

  2. web传输过程中的gzip压缩

    最近在做项目的时候用到了gzip,发现它的压缩能力还是很强大的,基本能够压缩50%的文本文件大小.以前有所了解,但不够深入,现在详细了解下. 什么是gzip 在哪里使用gzip gzip对于不同类型文 ...

  3. mac 内置PHP配置多站点

    1.修改/private/etc/hosts 文件,建议用编辑器打开 最后一行加入你的网站名称(自定义),参考如下: 127.0.0.1 www.MyObj.com   2.修改/private/et ...

  4. python3 opencv3 实现基本的人脸检测、识别功能

    一言不和,先上码子(纯新手,莫嘲笑) # encoding: utf-8 #老杨的猫,环境:PYCHARM,python3.6,opencv3 import cv2,os import cv2.fac ...

  5. 浅谈ES6新增数据类型:Symbol

    面试中喜闻乐见的问题就是问我们的ES6新增了哪些个新特性 这篇文章一起学习一下新增的数据类型:Symbol JS的原始数据类型:6种Boolean,String,Undefined,NULL,Numb ...

  6. react-native-printer

    react-native-printer A React Native Library to support USB/BLE/Net printer for Android platform Inst ...

  7. 【oracle】生成AWR报告

    [第一步]找到awrrpt.sql文件 [ora11g@vm-kvm11820-app ~]$ locate awrrpt.sql /DATA/opt/app/ora11g/product//rdbm ...

  8. java高级进阶

  9. AI之旅(3):升维与最小二乘法

    前置知识   矩阵的逆 知识地图   首先我们将了解一种叫升维的方法,用已有特征构造更多的特征.接着通过对空间与投影建立一定的概念后,推导出最小二乘法. 当特征数量不足时   在上一篇<初识线性 ...

  10. 关于charles抓不到js文件的问题

    在清理了cookies后重新家在页面, charles抓不到js文件. 后来在https://zhidao.baidu.com/question/1802495173294727507.html 看到 ...