0%

MakeFile基础

gcc和MakeFile基础

gcc常用参数

  • gcc -c 指的是只把源码编译为目标文件而不进行链接。如果GCC不带-C参数,编译一个源代码文件(test.c)。那么会自动将编译和链接一步完成,并生成可执行文件。对于多个文件,需要先编译成中间目标文件(一般是.o文件),在链接成可执行文件,一般习惯目标文件都是以.o后缀,也没有硬性规定可执行文件不能用.o文件。
  • gcc -o指的是output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。
  • 其他参数详见 https://www.runoob.com/w3cnote/gcc-parameter-detail.html

MakeFile是什么

  • 使用 GCC 编译器在 Linux 进行 C 语言编译,通过在终端执行 gcc 命 令来完成 C 文件的编译,如果我们的工程只有一两个 C 文件还好,需要输入的命令不多,当文 件有几十、上百甚至上万个的时候用终端输入 GCC 命令的方法显然是不现实的。如果我们能够 编写一个文件,这个文件描述了编译哪些源码文件、如何编译那就好了,每次需要编译工程的 时只需要使用这个文件就行了。这种问题怎么可能难倒聪明的程序员,为此提出了一个解决大 工程编译的工具:make,描述哪些文件需要编译、哪些需要重新编译的文件就叫做 Makefile, Makefile 就跟脚本文件一样,Makefile 里面还可以执行系统命令。使用的时候只需要一个 make命令即可完成整个工程的自动编译,极大的提高了软件开发的效率。

  • 在 Linux 下用的最多的是 GCC 编译器,这是个没有 UI 的编译器,因此 Makefile 就需要我们自己来编写了。

应用举例

  • 我们完成这样一个小工程,通过键盘输入两个整形数字,然后计算他们的和并将结果显示在屏幕上,在这个工程中我们有 main.c、input.c 和 calcu.c 这三个 C 文件和 input.h、calcu.h 这 两个头文件。其中 main.c 是主体,input.c 负责接收从键盘输入的数值,calcu.c 进行任意两个数 相加,其中 main.c 文件内容如下:
//main.c
#include <stdio.h>
#include "input.h"
#include "calcu.h"

int main(int argc, char *argv[])
{
int a, b, num;

input_int(&a, &b);
num = calcu(a, b);
printf("%d + %d = %d\r\n", a, b, num);
}

  • input.c 文件内容如下:
//input.c
#include <stdio.h>
#include "input.h"

void input_int(int *a, int *b)
{
printf("input two num:");
scanf("%d %d", a, b);
printf("\r\n");
}

  • calcu.c 文件内容如下:
#include "calcu.h"
int calcu(int a, int b)
{
return (a + b);
}
  • 文件 input.h 内容如下:
#ifndef _INPUT_H
#define _INPUT_H

void input_int(int *a, int *b);
#endif
  • 文件 calcu.h 内容如下:
#ifndef _CALCU_H
#define _CALCU_H

int calcu(int a, int b);
#endif
  • 假如使用gcc编译程序,那么此时需要再命令行输入
gcc main.c calcu.c input.c -o main

上面命令的意思就是使用 gcc 编译器对 main.c、calcu.c 和 input.c 这三个文件进行编译,编 译生成的可执行文件叫做 main

image-20220109170757542

执行程序用到的命令是./main,含义是执行当前目录下的main文件

makefile的重要性

可以看出我们的代码按照我们所设想的工作了,使用命令“gcc main.c calcu.c input.c -o main” 看起来很简单是吧,只需要一行就可以完成编译,但是我们这个工程只有三个文件啊!如果几 千个文件呢?再就是如果有一个文件被修改了,使用上面的命令编译的时候所有的文件都会重新编译,如果工程有几万个文件(Linux 源码就有这么多文件!),想想这几万个文件编译一次 所需要的时间就可怕。最好的办法肯定是哪个文件被修改了,只编译这个被修改的文件即可, 其它没有修改的文件就不需要再次重新编译了,为此我们改变我们的编译方法,如果第一次编译工程,我们先将工程中的文件都编译一遍,然后后面修改了哪个文件就编译哪个文件,命令 如下:

gcc -c main.c
gcc -c input.c
gcc -c calcu.c
gcc main.o input.o calcu.o -o main
  • 注意,gcc的-c选项的意思是将程序编译为.o文件但是不链接为最终的可执行文件,最后一句gcc main.o input.o calcu.o -o main的意思是将三个.o文件链接为一个可执行文件
  • 假如我们现在修改了 calcu.c 这个文件,只需要将 caclue.c 这一个文件重新编译成.o 文件,然后在将所有的.o 文件链接成可执行文件,只需要下面两条命令即可:
gcc -c calcu.c
gcc main.o input.o calcu.o -o main

makefile的作用

  • 如果工程没有编译过,那么工程中的所有.c 文件都要被编译并且链接成可执行程序。
  • 如果工程中只有个别 C 文件被修改了,那么只编译这些被修改的 C 文件即可。
  • 如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的 C 文件,并且 链接成可执行文件。

makeFile的使用

  • 在工程目录下创建名为“Makefile”的文件, 文件名一定要叫做“Makefile”!!!区分大小写的哦!
  • image-20220109171722524

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

  • 上述代码中所有行首需要空出来的地方一定要使用TAB键!不要使用空格键!这是 Makefile 的语法要求

  • image-20220109172156926

  • Makefile 编写好以后我们就可以使用 make 命令来编译我们的工程了,直接在命令行中输入make即可,make 命令会在当前目录下查找是否存在Makefile这个文件,如果存在的 话就会按照 Makefile 里面定义的编译方式进行编译

image-20220109172335268

MakeFile中一般存在的错误

  • Makefile 中命令缩进没有使用 TAB 键!
  • VI/VIM 编辑器使用空格代替了 TAB 键,修改文件/etc/vim/vimrc,在文件最后面加上如 下所示代码:
    • set noexpandtab

此时修改一下input.c文件,重新编译看结果

image-20220109172511759

可以看出因为我们修改了 input.c 这个文件,所以 input.c 和最后的可执行文 件 main 重新编译了,其它没有修改过的文件就没有编译

Makefile语法

image-20220109173249859

  • 比如image-20220109174534114
    • 这条规则的目标是 main,main.o、input.o 和 calcu.o 是生成 main 的依赖文件,如果要更新 目标 main,就必须先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也 必须更新“更新”就是执行一遍规则中的命令列表
    • 每条命令以tab开始
    • make 命令会为 Makefile 中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。
  • 重新看一下上面的代码
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
  • 首先更新第一条规则中的 main,第一条规则的目标成为默认目标,只要默认目标更新了那 么就认为 Makefile 的工作。在第一次编译的时候由于 main 还不存在,因此第一条规则会执行, 第一条规则依赖于文件 main.o、input.o 和 calcu.o 这个三个.o 文件,这三个.o 文件目前还都没 有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行以 main.o 为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc –c main.c”,这行命令很熟悉了吧,就是不链接编译 main.c,生成 main.o,其它两个.o 文件同理。 最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应 的命令不会执行,当我们想要执行 clean 的话可以直接使用命令make clean,执行以后就会删 除当前目录下所有的.o 文件以及 main,因此 clean 的功能就是完成工程的清理
  • image-20220109180157902
  • 可见这条命令将除了源文件和Makefile以外的编译产物都删除了

总结一下Makefile的编译过程

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

这就是 make 的执行过程,make 工具就是在 Makefile 中一层一层的查找依赖关系,并执行相应的命令。

Makefile 变量

  • Makefile 中的变量都是字符串

  • 实例

#Makefile 变量的使用
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
  • Makefile 中可以写注释,注释开头要 用符号“#”

  • 变量的引用方法是$(变量名)

不同赋值符号的区别

  • 幅值符=
    • 类似于引用传参,幅值的变量的值会随着被赋给它的变量的值的改变去改变
name = zzk
curname = $(name)
name = zuozhongkai
print:
@echo curname: $(curname)

​ 此时输出的是zuozhongkai,意味着变量的内容随着变量的值更新而更新

@的意思是使得make在执行的过程中输出执行过程,否则不会输出

  • 幅值符:=

    • 同样执行上面的代码,将=改为:=,则可见输出还是”zzk”,因为:=在幅值的时候不会采用变量修改后的值
  • 幅值符?=

    • curname ?= zuozhongkai的意思是,假如curname前面没有被赋值,那么此变量就是“zuozhongkai”, 如果前面已经赋过值了,那么就使用前面赋的值。
  • 幅值符+=

    • Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进 去,此时就要使用到符号“+=”,比如

    • objects = main.o inpiut.oobjects += calcu.o执行完之后,objects就变成了main.o input.o calcu.o

Makefile模式规则

  • 自动匹配

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

    • 当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,比如%.o : %.c中的%代表的是同样的内容

前面的代码可以修改如下:

objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)

%.o : %.c
#命令
clean:
rm *.o
rm main

Makefile自动化变量

  • 如何通过一行命令来从不同的依赖文件中生 成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所 定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,类似于python中的变量解包,将一个数组中的变量一个一个的拆出来。自动化变量只应该出现在规则的命令中。
  • image-20220110002525509
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
gcc -c $<
clean:
rm *.o
rm main

  • 上面的代码中,$<代表依赖文件(.c)的一系列集合

  • 上述规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当 我们输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。可是如果我们“手贱”,在工作目录下创建一个名为“clean”的文件,那就不一样了,当执行“make clean”的时 候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我 们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将 clean 声明为伪 目标,声明方式如下:

.PHONY : clean
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)

.PHONY : clean

%.o : %.c
gcc -c $<
clean:
rm *.o
rm main

  • 声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名 为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行

Makefile条件判断

语法

<条件关键字>
<条件为真时执行的语句>
endif

<条件关键字>
<条件为真时执行的语句>
else
<条件为假时执行的语句>
endif

条件关键字的组成

  • ifeqifneq,判断的是是否相等和是否不等
    • 语法
ifeq (<参数 1>, <参数 2>)
ifeq ‘<参数 1 >’,‘ <参数 2>’
ifeq “<参数 1>”, “<参数 2>”
ifeq “<参数 1>”, ‘<参数 2>’
ifeq ‘<参数 1>’, “<参数 2>”
  • ifdefifndef
ifdef <变量名>

​ 如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是 一个函数的返回值。ifndef 用法类似,但是含义用户 ifdef 相反。

Makefile函数

  • Makefile 支持函数,类似 C 语言一样,Makefile 中的函数是已经定义好的,我们直接使用, 不支持我们自定义函数。make 所支持的函数不多,但是绝对够我们使用了
$(函数名 参数集合)
${函数名 参数集合}
  • 可以看出,调用函数和调用普通变量一样,使用符号“$”来标识。参数集合是函数的多个 参数,参数之间以逗号“,”隔开函数名和参数之间以“空格”分隔开,函数的调用以“$”开 头。

sbust

  • 函数 subst 用来完成字符串替换,调用形式如下:
$(subst <from>,<to>,<text>)
  • 此函数的功能是将字符串中的内容替换为,函数返回被替换以后的字符 串,比如如下示例:
$(subst zzk,ZZK,my name is zzk)
  • 把字符串“my name is zzk”中的“zzk”替换为“ZZK”,替换完成以后的字符串为“my name is ZZK”。

patsubst

  • 函数 patsubst 用来完成模式字符串替换,使用方法如下:
$(patsubst <pattern>,<replacement>,<text>)
  • 此函数查找字符串中的单词是否符合模式,如果匹配就用来 替换掉,可以使用通配符“%”,表示任意长度的字符串,函数返回值就是替换后的字 符串。如果中也包涵“%”,那么中的“%”将是中的那个 “%”所代表的字符串,也就是说%代表的字符串的内容不变
$(patsubst %.c,%.o,a.c b.c c.c)
  • 将字符串a.c b.c c.c中的所有符合%.c的字符串,替换为%.o,替换完成以后的字 符串为“a.o b.o c.o”,注意此时a,b和c是不变的。

dir

  • 函数 dir 用来获取目录
$(dir <names…>)
  • 此函数用来从文件名序列中提取出目录部分,返回值是文件名序列的目录 部分
$(dir </src/a.c>)
  • 提取文件“/src/a.c”的目录部分,也就是“/src”。

notdir

  • 函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名
$(notdir <names…>)
  • 此函数用与从文件名序列中提取出文件名非目录部分
$(notdir </src/a.c>)
  • 提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。

foreach

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

wildcard

  • 通配符“%”只能用在规则中只有在规则中它才会展开,如果在变量定义函数使用时, 通配符不会自动展开,这个时候就要用到函数 wildcard
$(wildcard PATTERN…)

比如

$(wildcard *.c)

上面的代码是用来获取当前目录下所有的.c 文件,类似“%”

使用例

(文件夹下有a.c, b.cc.c三个文件)

#!/bin/bash
list=$(wildcard *.c)
files=$(foreach filename, $(list), $(filename) hello)
main:

@echo ${list}

@echo $(files)

.PHONY : clean
clean:
rm *.o

输出

image-20220110111644323