makefile

makefile抽象层面的理解

学习某一样东西之前一定要明确学习的目的,即学习了这项工具能解决一些什么问题,其优势是什么?

makefile的优势就是能够动态根据文件的新旧来决定是否编译对应的文件,倘若每次编译一个项目都重新编译,特别是大项目的时候,岂不是很浪费时间?makefile能自动根据依赖关系解决这个问题,对于已经编译过的文件不再重新编译,而只选择编译尚未编译(或者新)的文件。

那么如何实现自动编译新文件呢?makefile是如何识别哪些是新的文件哪些是旧的文件的呢?

这就是makefile的核心--依赖关系

makefile中的语句就是为描述各种依赖关系而建立的。

例如下面这个例子

main:arithmetic.o main.o myprint.o
main.o:main.c
gcc -c main.c -o main.o
arithmetic.o:arithmetic.c
gcc -c arithmetic.c -o arithmetic.o
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o

翻译过来,用通俗的话说就是:

  • 如果main的修改时间在arithmetic.o main.o myprint.o的修改时间之前,那么就更新arithmetic.o main.o myprint.o (实际上就是确保main是最新的,如果不是,就更新冒号右边的文件。下面的语句也是类似的)
  • 保证main.o是最新的
  • 保证arithmetic.o是最新的
  • 保证myprint.o是最新的

那么如何更新这个文件呢?就通过冒号:下面一行的语句(有时可省略)来进行更新的操作(gcc编译出新文件覆盖掉旧文件)

显然,如果每一个源程序都需要一一给出依赖关系那么这个工作量无疑是巨大的。为此makefile提供了一些更简便的操作方法来管理,如提供变量,嵌入shell语句等。这些操作方法使得makefile管理文件变得简单的同时,也使得makefile的学习成本增加。但是只要理解了上面makefile的基本原理,那么makefile中的其他高级操作不过是对这种建立依赖关系的方法进行了扩充,只要多背几条语句/语法规则即可。

makefile简单例子

下面,通过一个具体的实例来说明更新的过程。


main.c

#include <stdio.h>
#include "arithmetic.h"
#include "myprint.h" int main()
{
int a=1;
int b=1; printf("a+b=%d\n",add(a,b));
myprint();
printf("hello world!\n");
return 0;
}

myprint.h

#ifndef __MYPRINT
#define __MYPRINT #include <stdio.h>
void myprint(void); #endif

myprint.c

#include "myprint.h"

void myprint(void)
{
printf("my print\n");
}

arithmetic.h

#ifndef __ARITHMETIC
#define __ARITHMETIC int add(int a ,int b); #endif

arithmetic.c

#include "arithmetic.h"

int add(int a ,int b)
{
return a+b;
}

makefile

版本1

main:arithmetic.o main.o myprint.o
main.o:main.c
gcc -c main.c -o main.o
arithmetic.o:arithmetic.c
gcc -c arithmetic.c -o arithmetic.o
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o
.PHONY:clean
clean:
rm -rf *.o

版本2

main:arithmetic.o main.o myprint.o
main.o:main.c
gcc -c $^ -o $@
arithmetic.o:arithmetic.c
gcc -c $^ -o $@
myprint.o:myprint.c
gcc -c $^ -o $@
.PHONY:clean
clean:
rm -rf *.o

版本3

main:arithmetic.o main.o myprint.o

%.o:%.c
gcc -c $^ -o $@
.PHONY:clean
clean:
rm -rf *.o

版本4

OBJS = 	main.o\
myprint.o\
arithmetic.o main:$(OBJS) %.o:%.c
gcc -c $^ -o $@
.PHONY:clean
clean:
rm -rf *.o

语法讲解:

%.o:表示当前目录下所有的.o文件

%.c:表示当前目录下所有的.c文件

$^:表示目标文件,即:左边的文件

$@:表示所有的依赖文件,即:右边的所有文件

.PHONY:clean:命令行输入make clean时会执行clean中的语句,但不会生成clean目标

一些常用的结论:

  • 可以将所有的目标-依赖语句(不包括前面部分的变量初始化语句)都抽象称为如下的表示

    目标文件:依赖文件
    执行语句

    那么所有的执行语句执行与否取决于目标文件和依赖文件的更新时间的比较(为了简化思考可以直接认为是一定会执行的)

  • 执行语句实际上就是shell命令


一般能理解并能手动写出版本4的makefile就足够用了。上面例程是仿照下面的博客做的,并加入了自己的一些理解和修改,感谢下面博客博主的热心分享。

博客连接,由此入

具体的语法这里也不再给出具体说明,因为这些资源网络上各处都有。更重要的是对makefile思想以及抽象层面上的理解。

实战:对实际的代码进行分析

要分析的代码:

KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m := kernel_hello.o
modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
@make clear
clear:
@rm -f *.o *.cmd *.mod.c
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
@rm -f *.unsigned
clean:
@rm -f hello.ko

分析:

  • 等号的区别

    var ?= will_not_excute
    var = first_assign
    var = second_assign
    result1 = $(var)
    result2 := $(var)
    result3 += $(var)
    result3 += $(var)
    var = final_assign
    main:
    @echo var final val: $(var)
    @echo result1: $(result1)
    @echo result2: $(result2)
    @echo result3: $(result3) 结果:
    $ make
    var final val: final_assign
    result1: final_assign
    result2: second_assign
    result3: final_assign final_assign

    从代码的执行结果分析,可以得出几点结论.

    1. ?=只有该变量在整个makefile都没有被赋值的时候才进行赋值
    2. :=右边部分调用$(var)会得到var上一次的值
    3. =右边部分调用$(var)会得到var在整个makefile中最后的值
    4. +=不用多说,就是追加操作
  • @ 语句

    表示不把正在执行的语句打印出来(但仍然会打印语句执行的结果)

    main:
    echo will display this statement
    @echo will not display this statement $ make
    echo will display this statement
    will display this statement
    will not display this statement
  • $(MAKE)解析出来就是"make"

  • -C  指明跳转到内核源码目录下读取那里的Makefile

    M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile

    这两个参数怎么理解呢?

    一般来说,一些大的工程会有几百个文件夹,而且每个文件夹里面都可能存在Makefile,这些Makefile记录着它所在文件夹下的源文件的编译步骤,但仅仅这样是不够的,这些Makefile只是做着自己当前目录下的事情,它们之间还需要进行一定程度的交流,那么,顶层Makefile就出现了,这个顶层Makefile不断地通过-C M=参数执行其管理的子Makefile,就像一个统筹全局的军师.有了顶层Makefile那么不管多大工程都能顺利编译出来了,而且还对一些模块进行了解耦,方便管理与修改.

makefile个人理解的更多相关文章

  1. Linux下对于makefile的理解

    什么是makefile呢?在Linux下makefile我们可以把理解为工程的编译规则.一个工程中源文件不计数,其按类型.功能.模块分别放在若干个目录中,makefile定义了一系列的规则来指定,那些 ...

  2. Makefile 13——理解make的解析行为

    make是以从上到下的顺序读入Makefile中的内容的.然而,处理Makefile中的语句却并非完全从上到下. 大体上,make处理一个Makefile分为两个阶段.第一个阶段包含: 1.make读 ...

  3. Makefile学习(一)----初步理解

    一.我对makefile的理解: 经过一段时间对makefile的学习,我理解的makefile就是将程序员手动编译源文件的过程用一个脚本执行,这对于小型项目来说,程序员手动执行和用makefile来 ...

  4. Makefile 编译生成多个可执行文件

    CC = gcc CXX = g++ CFLAGS = -O -DDBG -D_REENTRANT -Wall -DUSE_PTHREAD -pthread LDFLAGS = -ldl -lnsl ...

  5. (二)u-boot2013.01.01 for TQ210:《Makefile分析》

           当时写的时候看的是2012-10版本的,但是略对比了一遍和2013.01.01没什么改动,所以这不影响对2013.01.01版本的makefile的理解.本文比较侧重于语法句意的分析,框 ...

  6. 一个通用的Makefile (转)

    据http://bbs.chinaunix.net/thread-2300778-1-1.html的讨论,发现还是有很多人在问通用Makefile的问题,这里做一个总结.也作为以后的参考.       ...

  7. linux 下makefile

    linux下c编程中makefile是必须会的,我刚开始学,将我对makefile的理解记录下来. 通常我们在windows下编写c程序,有各种ide工具为我们执行makefile工作但在linux下 ...

  8. OpenWRT添加模块 Makefile和Config.in

    添加模块编译 在网上找了一下,很多关于编译Openwrt系统的资料,不过这些事情芯片厂商提供的开发包都已经办得妥妥了,但是没有找到系统介绍的资料,添加一个包的介绍有不多,其中有两个很有参考价值: ht ...

  9. Ubuntu下比较通用的makefile实例

    本文转自http://blog.chinaunix.net/uid-20608849-id-360294.html  笔者在写程序的时候会遇到这样的烦恼:一个项目中可能会有很多个应用程序,而新建一个应 ...

随机推荐

  1. SuperSocket新的配置属性 "defaultCulture"

    这个新增的功能只支持 .Net framework 4.5 及其以上版本. 它允许你设置所有线程的默认Culture, 不管这些线程是如何创建,通过代码或者来自于线程池. 这个新的配置属性 " ...

  2. torch中的copy()和clone()

    torch中的copy()和clone() 1.torch中的copy()和clone() y = torch.Tensor(2,2):copy(x) ---1 修改y并不改变原来的x y = x:c ...

  3. 洛谷P3957 跳房子 题解 二分答案/DP/RMQ

    题目链接:https://www.luogu.org/problem/P3957 这道题目我用到了如下算法: 线段树求区间最大值: 二分答案: DP求每一次枚举答案g时是否能够找到 \(\ge k\) ...

  4. Node.js 安装及环境配置 以及google浏览器安装插件并使用

    一.安装环境 1.本机系统:Windows 10 企业版(64位)2.Node.js:node-v10.16.0-x64.msi(64位) 二.安装Node.js步骤 1.下载对应自己系统对应的 No ...

  5. Codeforces Round #198 (Div. 1 + Div. 2)

    A. The Wall 求下gcd即可. B. Maximal Area Quadrilateral 枚举对角线,根据叉积判断顺.逆时针方向构成的最大面积. 由于点坐标绝对值不超过1000,用int比 ...

  6. vue组件中data是个函数

    当我们const vm = new Vue({ el : '#app',   data : { msg:‘hello World’ } })用习惯了,data是一个对象,可到了vue组件 Vue.co ...

  7. redux.js的基本使用

    1.先是安装reduxJx, cnpm i --save rudux 2.创建一个store的js文件 3.使用import来引用 redux import { createStore } from ...

  8. records

    2019年数据地址备份: three.js 实例在NextWebProject/static/canvas下边! qlgj 在NextWebProject下边!

  9. hdu 6579 Operation (在线线性基)

    传送门 •题意 一个数组a有n个数 m个操作 操作① 询问$[l,r]$区间的异或值 操作② 在数组末尾追加一个数x,数组长度变为$n+1$ 其中$l,r$不直接给出,其中$l=l%n+1,r=r%n ...

  10. 网易Java高级开发课程随笔

    java学习也有6个月之久,记录下课程相关知识点,目前我还没有掌握,so仅作技术点记录 鉴于在.NET上我封装了一套开发框架,虽去年按.NET封装的思路自己也弄了个java开发框架,还是感觉对java ...