• makefile记录


    1、基本编译流程

    发行版linux系统都会安装gcc,可以通过下面的命令查看gcc版本,如下所示,我这里是ubuntu16.04:
    在这里插入图片描述
    gcc 命令格式如下:

    gcc [选项] [文件名字]
    
    • 1

    主要选项如下:

    • -c: 只编译不链接为可执行文件,编译器将输入的.c 文件编译为.o 的目标文件。
    • -o: <输出文件名>用来指定编译结束以后的输出文件名,如果不使用这个选项的话 GCC 默认编译出来的可执行文件名字为 a.out。
    • -g: 添加调试信息,如果要使用调试工具(如 GDB)的话就必须加入此选项,此选项指示编译的时候生成调试所需的符号信息。
    • -O: 对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进行优化,这样产生的可执行文件执行效率就高。
    • -O2: 比-O 更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。

    编译的流程:

    下面的图来自从零0⃣️开始写Makefile【新手教程】
    在这里插入图片描述

    因此总结一下:GCC 编译器的编译流程是:预处理、编译、汇编和链接

    预处理就是展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中。编译是将经过预编译处理的代码编译成汇编代码,也就是我们常说的程序编译。汇编就是将汇编语言文件编译成二进制目标文件。链接就是将汇编出来的多个二进制目标文件链接在一起,形成最终的可执行文件,链接的时候还会涉及到静态库和动态库等问题。

    整个流程如下所示:
    在这里插入图片描述

    2、最基本的makefile

    那么什么是makefile呢?通过在终端执行 gcc 命令来完成 C 文件的编译,如果我们的工程只有一两个 C 文件还好,需要输入的命令不多,当文件有几十、上百甚至上万个的时候用终端输入 GCC 命令的方法显然是不现实的。如果我们能够编写一个文件,这个文件描述了编译哪些源码文件、如何编译那就好了,每次需要编译工程的时只需要使用这个文件就行了。这个文件就是makefile文件了。

    下面用一个最基础的例子来说明什么是makefile:

    下面是一个最基本的makefile文件
    在这里插入图片描述
    下面是运行他需要的外部函数:
    在这里插入图片描述
    这样如果要写一个makefile来编译这个工程,(makefile需要我们创建一个名为makefile的文件,make进行编译的时候会自动寻找这个文件进行编译)写法如下:
    在这里插入图片描述
    在使用的时候,也只需要make一下即可,之后运行就可以看到结果了,如下所示:
    在这里插入图片描述
    可以看到这个其实是跟gcc的编译好像没什么区别,那这样还不如gcc直接输入一下不是也很方便吗,的确,当文件比较少的时候无法凸显出makefile强大的功能,下面就了解一下makefile的基本规则再来看看吧。

    3、makefile的基本规则

    1、基本使用

    makefile的基本规则格式如下:

    目标…... : 依赖文件集合……
    命令 1
    命令 2
    ……
    
    • 1
    • 2
    • 3
    • 4

    上面的例子不是很据有代表性,下面我用一个makefile的模版来说明一下:

    main: main.o input.o calcu.o
    	gcc -o main main.o input.o calcu.o
    main.o: main.c
    	gcc -c main.c
    input.o: input.c
    	gcc -c input.c
    calcu.o: calcu.c
    	gcc -c calcu.c
    
    clean:
    	rm *.o
    	rm main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的makefile中根据最开头的语法规则来看,第一条是默认规则,就是main,main依赖于main.o,input.o,calcu.o这几个文件,然后下面三个就指明了其他main.o等这个几个的依赖,比如main.o是依赖于main.c然后需要gcc来编译这个命令,最后有一个clean的项,但是clean需要我们make clean才会执行,但是第一个main的命令则不需要,因为第一行是默认命令,没有其他干扰会首先执行第一条命令。

    因此来总结一下makefile的工作流程:

    • 1、 make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。
    • 2、当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
    • 3、当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。

    2、相关规则

    1、变量

    跟 C 语言一样 Makefile 也支持变量的,先看一下前面的例子:
    在这里插入图片描述
    这里例如main.o input.o calcu.o这三个变量我们输入了两遍,如果过程很复杂还有输入很多遍,因此就可以省略的写法,如下所示:
    在这里插入图片描述不像 C 语言中的 Makefile 中的变量都是字符串,类似 C 语言中的宏。(这里补充一点,就是makefile的注释符号是#),Makefile 中变量的引用方法是“$ (变量名)”,比如本例中的“$ (objects)”就是使用变量 objects。

    2、赋值符

    Makefile变量的赋值符有“=“,”:=”和“?=”这三种,他们的用法如下所示:

    = 就是直接赋值的意思了,这个很容易理解,但是要注意一点就是,这个赋值符以最新的为准,就是前面赋值一次,后面修改了这个变量,以最新修改的为准:
    在这里插入图片描述

    := 这个赋值符的区别就在于,他以第一次的赋值结果为准,不用后面修改的值
    在这里插入图片描述
    ?=直接看下面的例子吧

    name ?= zzk
    
    • 1

    如果变量 name 前面没有被赋值,那么此变量就是“zzk”,如果前面已经赋过值了,那么就使用前面赋的值。

    += 这个用于变量是字符串的时候,用于追加复制,可以理解为字符串的拼接,如下所示:

    name = main.o input.o 
    name += calcu.o
    
    • 1
    • 2

    3、模式规则

    上述 Makefile 将对应的.c 源文件编译为.o 文件,每一个 C 文件都要写一个对应的规则,如果工程中 C文件很多的话显然不能这么做。为此,我们可以使用 Makefile 中的模式规则:

    通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。模式规则中,至少在规则的目标定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符, a.%.c 就表示以 a.开头,以.c 结束的所有文件。

    例如下面的命令

    %o:%c
    	cmd
    
    • 1
    • 2

    4、自动化变量

    上面的规则都是目标和依赖都是一系列的文件,每次模式规则进行解析都是不同的目标和依赖,但是命令只有一行,如何通过一行命令从不同的依赖中生成对应的目标,需要我们使用自动化变量来实现,下面例举了常见的自动化变量:

    自动化变量描述
    $@规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模式中定义的目标集合。
    $%当目标是函数库的时候表示规则中的目标成员名,如果目标不是函数库文件,那么其值为空。
    $<依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%” )定义的,那么“$<”就是符合模式的一系列的文件集合。
    $?所有比目标新的依赖目标集合,以空格分开。
    $^所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,
    $+和“$^”类似,但是当依赖文件存在重复的话不会去除重复的依赖文件。
    $*这个变量表示目标模式中"%"及其之前的部分,如果目标是 test/a.test.c,目标模式为 a.%.c,那么“$*”就是 test/a.test。

    这样上面的代码可以修改为:
    在这里插入图片描述
    这里就是指定所有的依赖文件了

    5、伪目标

    Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。使用伪目标主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些令,但是这个规则不是用来创建文件的,

    比如下面的,我们一般会写一个clean的目标,方便我们清理掉一些过程文件:
    在这里插入图片描述
    但是如果正好有一个文件名字是clean的话,那就不一样了,当执行make clean的时候,规则因为没有依赖文件,因此目标被认为是最新的,后面的命令就不会继续执行了,而是重新编译,因此我们可以把clean声明为伪目标,声明的方式如下:

    .PHONY:clean
    
    • 1

    最终如下所示:
    在这里插入图片描述

    6、条件判断

    在这里插入图片描述
    其中条件关键字有 4 个: ifeq、 ifneq、 ifdef 和 ifndef,这四个关键字其实分为两对、 ifeq 与 ifneq、 ifdef 与 ifndef,他们的用法如下所示:

    ifeq(<参数1>,<参数2>)
    判断两个参数是否相同,相同为真,参数12可以作为函数返回值
    
    ifneq(<参数1>,<参数2>)
    判断两个参数是否不相同,不相同为真,参数12可以作为函数返回值
    
    ifdef<变量名>
    变量名的值非空表达式为真
    ifndef<变量名>
    变量名的值为空表达式为真
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    7、函数使用

    Makefile 支持函数,类似 C 语言一样, Makefile 中的函数是已经定义好的,我们直接使用,不支持我们自定义函数。 make 所支持的函数不多,但是绝对够我们使用了,函数的用法如下:

    $(函数名 参数集合) 这里()可以用{}代替

    1、函数subst 字符串替换函数

    $(subst <from>,<to>,<text>) 将test中的from换成to
    
    • 1

    2、函数patsubst 完成模式字符串替换

    $(patsubst <pattern>,<replacement>,<text>)
    
    此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来替换掉, 
    <pattern>可以使用通配符“%”,表示任意长度的字符串,函数返回值就是替换后的字符串。如果
    <replacement>中也包涵“%”,那么<replacement>中的“%”将是<pattern>中的那个“%”所代表的字符串。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、函数dir获取目录

    $(dir <names…>) 这里就是获取<names>的目录部分
    
    • 1

    4、函数notdir 去除文件中的目录部分

    $(notdir <names…>) 提取文件名
    
    • 1

    5、 函数foreach 完成循环

    $(foreach <var>, <list>,<text>)
    
    此函数的意思就是把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行<text>所
    包含的表达式。每次<text>都会返回一个字符串,循环的过程中, <text>中所包含的每个字符串
    会以空格隔开,最后当整个循环结束时, <text>所返回的每个字符串所组成的整个字符串将会是
    函数 foreach 函数的返回值。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6、函数 wildcard :通配符在变量定义和函数使用的时候不会自动展开,需要用到wildcard这个函数

    $(wildcard PATTERN…)
    
    • 1

    例如我们要获取当前目录下所有的.c 文件,类似“%”。

    $(wildcard *.c)
    
    • 1

    4、一些好用的makefile模版

    这个算一个更新吧,等有了再说,下面是一个编译当前目录下所有.c文件的模版

    SOURCE = $(wildcard *.c)
    TARGETS = $(patsubst %.c, %, $(SOURCE))
    
    CC = gcc
    CFLAGS = -Wall -g -lm#如果有math.h要加上这个lm选项
    
    all:$(TARGETS)
    
    $(TARGETS):%:%.c
    		$(CC) $< $(CFLAGS) -o $@
    
    .PHONY:clean all
    clean:
    	-rm -rf $(TARGETS)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    普林斯顿微积分读本第一章--函数、反函数
    【博客434】Kubelet Bootstrap Checkpoint
    Matlab实现遗传算法仿真(附上20个仿真源码)
    【简单模拟】第十二届蓝桥杯省赛第一场C++ B组/C组/研究生组《时间显示》(c++)
    Linux进程常见通信方式
    数据指标体系建设思考(二)
    [Note] 汉明码与汉明距离的思考
    专车数据层架构进化往事:好的架构是进化来的,不是设计来的
    【课程设计|MFC】火车票售票系统(含课程报告+源码)
    【计算机毕业设计】5.网上书店系统maven源码
  • 原文地址:https://blog.csdn.net/m0_51220742/article/details/126198776