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. hdu 1156 && poj 2464 Brownie Points II (BIT)

    2464 -- Brownie Points II Problem - 1156 hdu分类线段树的题.题意是,给出一堆点的位置,stan和ollie玩游戏,stan通过其中一个点画垂线,ollie通 ...

  2. 【codeforces 777E】Hanoi Factory

    [题目链接]:http://codeforces.com/problemset/problem/777/E [题意] 让你摆汉诺塔片; 要求在上面的片的外圈大于在下面的片的内圈,且小于下面的片的外圈; ...

  3. H3C 以太网集线器

  4. SuperSocket 中内置的 Flash/Silverlight 策略服务器

    关键字: 策略服务器, Flash策略服务器, Silverlight策略服务器, Policy Server, Flash Policy Server, Silverlight Policy Ser ...

  5. Python基础知识汇总

    1.执行脚本的两种方式 Python a.py     直接调用Python解释器执行文件 chomd +x a.py   ./a.py    #修改a.py文件的属性,为可执行,在用  ./  执行 ...

  6. java反斜杠替换

    java replaceAll() 方法要用 4 个反斜杠,表示一个反斜杠 例如 str1="aa\bbb"; str2="aa'bbb"; 要想替换成 str ...

  7. html(四)数据库curd操作与分页查询

    数据库操作curd : 1.首先要建立项目处理好自己逻辑包: 其中util工具包中建立两个工具类 jdbc连接和page分页 DBUtil.java: db工具类就是用于连接数据库的jdbc架包,里面 ...

  8. Python--day39--管道和数据共享(面试可能会问到)

    1,管道 上面所述挂起即为阻塞 管道.py from multiprocessing import Pipe, Process def func(conn1,conn2): conn2.close() ...

  9. Java中的元注解

    注解为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据. 通过使用注解,我们可以将这些元数据保存在Java源代码中,并利用annotation API为自己的 ...

  10. Codeforces Round #529 (Div. 3) E. Almost Regular Bracket Sequence(思维)

    传送门 题意: 给你一个只包含 '(' 和 ')' 的长度为 n 字符序列s: 给出一个操作:将第 i 个位置的字符反转('(' ')' 互换): 问有多少位置反转后,可以使得字符串 s 变为&quo ...