• 嵌入式Linux应用开发基础知识(四)——Makefile语法


    前面我们介绍了Makefile运行规则,这一章我们介绍Makefile的语法。

    1. 通配符

    什么是通配符?学过其他编程语言的可能只要什么叫通配符,通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符。

    1.1 Makefile常见通配符

    Makefile具有较多通配符,但是常见的也就几种。如下所示:

    • %.o或%.c : 该通配符表示任意的.o文件或者.c文件
    • $@ : 表示目标
    • $< : 表示第一个依赖文件
    • $^ : 表示所有依赖文件

    1.2 Makefile常见通配符实验

    以下为上节课我们使用的Makefile文件

    test : main.o sub.o
            gcc -o test main.o sub.o
    main.o : main.c
            gcc -c -o main.o main.c
    sub.o : sub.c
            gcc -c -o sub.o sub.c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们将使用通配符进行改进,实现我们使用%.o和%.c文件进行改进,

    test : main.o sub.o
            gcc -o test main.o sub.o
    %.o : %.c
            gcc -c -o main.o main.c
    
    • 1
    • 2
    • 3
    • 4

    运行逻辑:

    • 当一开始运行程序时,没有main.o,那么会向下查找,发现通配符%.o, 那么此时将%替换为main,执行: gcc -c -o main.o main.c 命令
    • 生成main.o后,再次查找依赖sub.o, 发现没有改文件,那么需要往下查找,此时将%替换为sub, 执行gcc -c -o main.o main.c命令,不过意外的是,这里还是生成main.o, 最后还会编译不了 test, 系统报错,相应过程见下图。
      在这里插入图片描述
      需要对上述Makefile进行修改,使用
    test : main.o sub.o
            gcc -o test main.o sub.o
    %.o : %.c
            gcc -c -o $@ $<
    
    • 1
    • 2
    • 3
    • 4

    $@表示目标 , $<表示第一个依赖文件, 那么当%为main时,此时 $@ 表示main.o, $<表示main.c。当%为sub时,此时 $@ 表示sub.o, $<表示sub.c。 具体运行过程见下图:

    在这里插入图片描述
    这样就能正常运行了, 不过是否考虑到一个问题,如果有很多OBJ文件呢? 难道我们需要一个一个加上XX.o吗,那么此时gcc -o test main.o sub.o这条语句得使劲加?显然这样是不智能的,我们可以使用$^ : 通配符,表示所有依赖文件,改进的程序如下:

    test : main.o sub.o
            gcc -o test $^
    %.o : %.c
            gcc -c -o $@ $<
    
    • 1
    • 2
    • 3
    • 4

    当运行 gcc -o test $^ 命令时,程序会将 $^ 替换为main.o sub.o, 这样的话我们有多个源文件,只需要往sub.o后添加就可以了。下图为运行过程图:
    在这里插入图片描述

    2. 假想目标

    我们先不提什么是假想目标,我们慢慢引入。

    2.1 清除中间文件

    我们希望删除中间文件(.o文件)和目标文件(test), 以确保我们将编译过程产生的文件全部删除,我们可以在Makefile文件中加入 rm *.o test命令,其中 *.o表示全部的OBJ文件,test为可执行程序。那这条语句应该怎么加入到Makefile文件中呢?我们可以仿造一下,直接写clean: (不需要依赖文件)。当我们输入make时(不带任何参数),此时系统会找到文件的第一个目标文件(第一行的开头的目标:test), 默认执行make test, 此时系统运行第一行,在清楚时,输入make clean, 系统会跳转到clean处,由于不需要依赖文件,所以任何时间都会运行,此时执行rm *.o test 以下为所有代码

    test : main.o sub.o
            gcc -o test $^
    %.o : %.c
            gcc -c -o $@ $<
    clean:
    		rm *.o test
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    下图为实验过程:
    在这里插入图片描述

    2.2 本地存在clean文件

    是否想过一个问题,如果在当前目录存在clean文件,那么当我们运行make clean时会出现什么情况?
    在这里插入图片描述

    如上图所示:我们使用 > clean命令创建一个空的clean文件,然后再执行make clean, 此时发现显示:make: ‘clean’ is up to date. 这个报错表示clean文件是最新的, 不需要再执行了,这样就不能执行make clean操作了,此时我们可以使用.PHONY: clean 设置clean为假想目标,详细代码如下:

    test : main.o sub.o
            gcc -o test $^
    %.o : %.c
            gcc -c -o $@ $<
    clean:
    		rm *.o test
    .PHONY: clean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    下图为详细的试验过程
    在这里插入图片描述

    3. 变量

    3.1 分类及定义

    Makefile的变量分为即时变量(也称简单变量)、延时变量,即时变量是在定义时就确定值,而延时变量是在使用时才确定的值,在没使用 时一直为空,以下为使用举例:

    • A := xxx # A的值即刻确定,在定义时即确定
    • B = xxx # B的值使用到时才确定

    3.2 实验

    3.2.1 使用变量

    Makefile中使用变量使用$(变量名称),代码中的all和之前讲的clean类似, 由于代码中只有一个目标文件all,所以在运行时输入make和make all的效果是一样的,echo是Makefile中的打印函数,测试代码如下:

    A := abc
    B = 123
    
    all:
            echo $(A)
            echo $(B)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行结果:
    在这里插入图片描述
    上述图中echo abc和echo 123是打印命令,如果不想打印,可以在echo前面加入@,代码如下:

    A := abc
    B = 123
    
    all:
            @echo $(A)
            @echo $(B)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行结果:
    在这里插入图片描述
    此时我们还是看不出即时变量和延时变量的区别,接着我们引入一个新变量C,代码如下:

    A := $(C)
    B = $(C)
    C = abc
    
    all:
            @echo $(A)
            @echo $(B)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述
    有上图可知,A变量值为空, B变量的值为abc。
    运行机制如下:运行A := $©时,由于C变量未定义,而且A为即时变量,此时A的值已经确定(为空),运行B = $©时,即使C变量未定义,但是B为延时变量,在引用时才确定值,运动C = abc,定义了延时变量C,运行@echo $(A)直接打印A的值(空),运行@echo $(B)此时先引用B,在引用时C变量已经定义,有值(abc),故此时B的值也确定了(也为abc),故打印abc。
    为了更加清晰的看出变量的值,我们加上辅助信息,代码如下:

    A := $(C)
    B = $(C)
    C = abc
    
    all:
            @echo A = $(A)
            @echo B = $(B)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述

    有人会想是否和C = abc的位置有关呢?我们修改代码如下:

    A := $(C)
    B = $(C)
    # C = abc
    
    all:
            @echo A = $(A)
            @echo B = $(B)
    C = abc
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述
    由运行结果可知,可位置是没关系的,是因为Makefile文件在运行前会将所有代码读入,当运行make或make all时才生产目标文件。那么我们是否会考虑代码加载的顺序?是从上到下加载吗?我们不妨做个实验:

    A := $(C)
    B = $(C)
    C = abc
    
    all:
            @echo A = $(A)
            @echo B = $(B)
    C = 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    观察以上代码,如果打印B=123,表示是顺序执行的,并且后面的值覆盖了前面的值
    运行结果:
    在这里插入图片描述
    从上图结果可以看出,的确和我们的猜想是一样的。

    3.3 变量运算及赋值方式

    3.3.1 常见的赋值方式

    • ?= : 也是延时变量赋值的一种,区别在于如果是第一次定义才起效,如果在前面该变量已定义则忽略这句。
    • +=:附加,它是即时变量还是延时变量取决于前面的定义

    3.3.2 赋值方式实验

    我们做 += 的实验,运行如下代码:

    A := $(C)
    B = $(C)
    C = abc
    
    all:
            @echo A = $(A)
            @echo B = $(B)
    C += 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述
    由结果可知,C还是为即时变量,因为之前是采用 =进行定义的。

    我们再做 ?= 的实验,运行如下代码:

    A := $(C)
    B = $(C)
    C = abc
    
    D = LingTu
    D ?= LLLS
    
    all:
            @echo A = $(A)
            @echo B = $(B)
            @echo D = $(D)
    C += 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:
    在这里插入图片描述
    由上图可知,D = LingTu在D ?= LLLS之前定义了, 故D ?= LLLS定义不起效果。
    我们再做一个对比,代码如下:

    A := $(C)
    B = $(C)
    C = abc
    
    D ?= LLLS
    
    all:
            @echo A = $(A)
            @echo B = $(B)
            @echo D = $(D)
    C += 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行结果:
    在这里插入图片描述
    此时D ?= LLLS为第一次定义变量,故起效果。
    除了在代码中幅值外,我们也可以在输入命令时赋值,命令行的赋值先于代码中的赋值,实验如下:
    在这里插入图片描述
    注意命名行赋值时参数不要输入空格,命令行是默认以空格间隔参数的,第一次加入空格报错了。

  • 相关阅读:
    1225. 报告系统状态的连续日期
    编码技巧——MongoDB的过期时间
    中文drupal教程(3)响应对象Response及Cookie设置
    原生Js 提取视频中的音频
    C++之STL中vector的使用
    初步认识Java
    Gartner 存储与数据保护技术 Hype Cycle 解读|SmartX 趋势分享
    Python爬虫实战,requests+xlwings模块,Python实现制作天气预报表!
    HashTable、HashMap、ConcurrentHashMap之间的区别
    Map及其实现类、锁
  • 原文地址:https://blog.csdn.net/FourLeafCloverLLLS/article/details/126194746