C语言-Makefile文件—静态库与动态库

        前面我们的课里写的源码文件,也写了一些文件例子,最多也就几个,我们还可以用gcc .c一下子联合编译了,但到了真正的项目工程里,源文件是不计其数的,其按照类型,功能,模块分别放在若干个目录中,哪些文件需要先编译,那些文件需要后编译,那些文件需要重新编译,甚至进行更复杂的功能操作,这时就必须引入了系统编译的工具,能管理整个工程的工具:
这个工具叫make,可以用它来管理多模块程序的编译和链接,直至生成可执行文件
        make命令执行的时候需要一个说明文件,这个文件名字默认叫
Makefile,Makefile文件中描述了整个软件工程的编译规则和各个文件之间的依赖关系,我们把这个文件里的内容叫规则;
        Makefile就像是一个脚本程序一样,它带来的好处就是:一旦写好Makefile文件里的规则,只要一个make命令,整个工程就会自动编译,自动生成最终的可执行文件,能极大的提高了软件开发的效率;

1,先一个简单例子,示范一下如何写Makefile里的规则:

        首先:回顾确认安装MinGW64的时候,复制mingw32-make.exe并重命名为make.exe,或者在Linux环境安装了make工具(sudo apt-get install -y make),
在这里插入图片描述

新建main.c 实现代码如下:

#include<stdio.h>
#include<stdlib.h>
#include "max.h"
 
int main(void)
{
    printf("The bigger one of 3 and 5 is%d\n", max(3, 5));
    system("pause");
    return 0;
}

新建max.h 实现代码如下:

int max(inta, int b);

新建max.c实现代码如下:

#include"max.h"
int max(inta, int b)
{
    return a > b ? a : b;
}

新建Makefile 实现代码如下:
以前我们是gcc -g main.c max.c -o main.exe,这样显然当文件非常多时,不得行的。

main:main.o max.o   #规则的模式
       gcc -o main main.o max.o    #规则的命令
 
main.o:main.c max.h
       gcc -c main.c
 
max.o:max.c max.h
       gcc -c max.c

在这里插入图片描述
说明:
所有gcc的行前面为制表符(tap),否则保存后无法编译通过。
打开命令提示窗口,进入该工程目录,键入make命令执行,生成文件,包含中间文件*.o,*.exe(Win下才有)

2、make和Makefile的基本知识点

  1. make的使用语法:
    make [选项][目标]
    选项列表:
    -h 显示所有选项的简要说明(help)
    -d 显示调试信息(debug)
    -s 安静的方式运行,不显示任何信息(silence)
参数选项功能
-b,-m忽略,提供其他版本 make 的兼容性
-B,–always-make强制重建所有的规则目标,不根据规则的依赖描述决定是否重建目标文件。
-C DIR,–directory=DIR在读取 Makefile 之前,进入到目录 DIR,然后执行 make。当存在多个 “-C” 选项的时候,make 的最终工作目录是第一个目录的相对路径。
-dmake 在执行的过程中打印出所有的调试信息,包括 make 认为那些文件需要重建,那些文件需要比较最后的修改时间、比较的结果,重建目标是用的命令,遗憾规则等等。使用 “-d” 选项我们可以看到 make 构造依赖关系链、重建目标过程中的所有的信息。
–debug[=OPTIONS]make 执行时输出调试信息,可以使用 “OPTIONS” 控制调试信息的级别。默认是 “OPTIONS=b” ,“OPTIONS” 的可值为以下这些,首字母有效:all、basic、verbose、implicit、jobs、makefile。
-e,–enveronment -overrides使用环境变量定义覆盖 Makefile 中的同名变量定义。
-f=FILE,–file=FILE,–makefile=FILE指定文件 “FILE” 为 make 执行的 Makefile 文件
-p,–help打印帮助信息。
-i,–ignore-errors执行过程中忽略规则命令执行的错误。
-I DIR,–include-dir=DIR指定包含 Makefile 文件的搜索目录,在Makefile中出现另一个 “include” 文件时,将在 “DIR” 目录下搜索。多个 “-i” 指定目录时,搜索目录按照指定的顺序进行。
-j [JOBS],–jobs[=JOBS]可指定同时执行的命令数目,爱没有 “-j” 的情况下,执行的命令数目将是系统允许的最大可能数目,存在多个 “-j” 目标时,最后一个目标指定的 JOBS 数有效。
-k,–keep-going执行命令错误时不终止 make 的执行,make 尽最大可能执行所有的命令,直至出现知名的错误才终止。
-l load,–load-average=[=LOAD],–max-load[=LOAD]告诉 make 在存在其他任务执行的时候,如果系统负荷超过 “LOAD”,不在启动新的任务。如果没有指定 “LOAD” 的参数 “-l” 选项将取消之前 “-l” 指定的限制。
-n,–just-print,–dry-run只打印执行的命令,但是不执行命令。
-o FILE,–old-file=FILE,–assume-old=FILE指定 "FILE"文件不需要重建,即使是它的依赖已经过期;同时不重建此依赖文件的任何目标。注意:此参数不会通过变量 “MAKEFLAGS” 传递给子目录进程。
-p,–print-date-base命令执行之前,打印出 make 读取的 Makefile 的所有数据,同时打印出 make 的版本信息。如果只需要打印这些数据信息,可以使用 “make -qp” 命令,查看 make 执行之前预设的规则和变量,可使用命令 “make -p -f /dev/null”
-q,-question称为 “询问模式” ;不运行任何的命令,并且无输出。make 只返回一个查询状态。返回状态 0 表示没有目标表示重建,返回状态 1 表示存在需要重建的目标,返回状态 2 表示有错误发生。
-r,–no-builtin-rules取消所有的内嵌函数的规则,不过你可以在 Makefile 中使用模式规则来定义规则。同时选项 “-r” 会取消所有后缀规则的隐含后缀列表,同样我们可以在 Makefile 中使用 “.SUFFIXES”,定义我们的后缀名的规则。“-r” 选项不会取消 make 内嵌的隐含变量。
-R,–no-builtin-variabes取消 make 内嵌的隐含变量,不过我们可以在 Makefile 中明确定义某些变量。注意:“-R” 和 “-r” 选项同时打开,因为没有了隐含变量,所以隐含规则将失去意义。
-s,–silent,–quiet取消命令执行过程中的打印。
-S,–no-keep-going,–stop取消 “-k” 的选项在递归的 make 过程中子 make 通过 “MAKEFLAGS” 变量继承了上层的命令行选项那个。我们可以在子 make 中使用“-S”选项取消上层传递的 “-k” 选项,或者取消系统环境变量 “MAKEFLAGS” 中 "-k"选项。
-t,–touch和 Linux 的 touch 命令实现功能相同,更新所有的目标文件的时间戳到当前系统时间。防止 make 对所有过时目标文件的重建。
-v,version查看make的版本信息。
-w,–print-directory在 make 进入一个子目录读取 Makefile 之前打印工作目录,这个选项可以帮助我们调试 Makefile,跟踪定位错误。使用 “-C” 选项时默认打开这个选项。
–no-print-directory取消 “-w” 选项。可以是 用在递归的 make 调用的过程中 ,取消 “-C” 参数的默认打开 “-w” 的功能。
-W FILE,–what-if=FILE,–new-file=FILE,–assume-file=FILE设定文件 “FILE” 的时间戳为当前的时间,但不更改文件实际的最后修改时间。此选项主要是为了实现对所有依赖于文件 “FILE” 的目标的强制重建。
–warn-undefined-variables在发现 Makefile 中存在没有定义的变量进行引用时给出告警信息。此功能可以帮助我们在调试一个存在多级嵌套变量引用的复杂 Makefile。但是建议在书写的时候尽量避免超过三级以上的变量嵌套引用。
  1. Makefile的编写规则
目标列表:关联性文件列表
<TAB>命令列表

关联性文件中的某个为子目标:关联性列表
<TAB>命令列表
.....
最终的关联文件都是我们已经写好的.h和.c文件

目标:是用一个或者多个空格分开的目标文件的清单
关联性列表:目标所依赖的一个或多个文件,中间也是用空格分隔;
命令列表:用于创建目标文件命令或者目标对应需要执行的命令,一行太长可以换行:\,
如:

main:main.o max.o
       gcc -o main \
       main.o max.o
 
main.o:main.c max.h
       gcc -c main.c
 
max.o:max.c max.h
       gcc -c max.c

注意:
(1)书写的形式类似于倒推的形式。
(2)make去读取makefile的时候,是按照依赖文件的顺序去查找并且执行命令的。
(3)make在编译的时候,如果发现被编译的文件已经是最新的了,就不会再去编译(减少了编译次数)。
(4)make文件是根据被编译文件的时间戳去判断文件是否是当前最新的文件。
(5)千万要注意makefile中编译命令前面的分隔符。

3.make的工作过程
    (1)make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
    (2)如果找到,它会找文件中的第一个目标文件“main”,发现需要main.o和max.o。
    (3)如果main文件不存在,或是main所依赖的后面的 .o 文件的文件修改时间要比main这个文件新,
且main需要的.o文件都存在,那么,他就会执行后面所定义的命令来生成main这个文件。
如果.o文件不存在,这个时候make就会在Makefile文件中找到目标能匹配main.o和max.o的规则。
    (4)重复上面的逻辑过程。
    (5)最终,最下面的C和H文件是存在的,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件main了。

总结:
        make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

Makefile伪目标

       所谓的伪目标可以这样来理解,它并不会创建目标文件,只是想去执行这个目标下面的命令。
       如果需要书写这样一个规则,规则所定义的命令不是去创建文件,而是明确指定它来执行一些特定的命令。
实例:

main:main.o max.o
       gcc -o main \
       main.o max.o
 
main.o:main.c max.h
       gcc -c main.c
 
max.o:max.c max.h
       gcc -c max.c
clean:
     del *.o main.exe // 在linux中: rm -rf *.o test

       当工作目录下不存在以 clean 命名的文件时,在 shell 中输入 make clean 命令,命令 rm -rf *.o test 总会被执行 ,这也是我们期望的结果。
       如果当前目录下存在文件名为 clean 的文件时情况就会不一样了, make clean由于这个规则没有依赖文件,所以就不去执行规则下面的命令,因为它找到了项目中有clean文件了,觉得是有最新的。

处理。将一个目标声明称伪目标,写法:

.PHONY:clean

       这样,无论当前目录下是否存在 clean 这个文件,当我们执行 make clean 后 rm 都会被执行。
       我们建议在书写伪目标的时候,需要声明目标是一个伪目标,之后才是伪目标的规则定义。
目标 “clean” 的完整书写格式如下:

.PHONY:clean
clean:
    rm -rf *.o test  //windows: del *.o main.exe

Makefile变量的定义和使用

       Makefile 文件和编程其他编程文件一样,也是支持变量的:
变量的名称=值列表 #可以是零项,又可以是一项或者是多项,值也没弯弯绕,就是字符串
变量的名称可以由大小写字母、阿拉伯数字和下划线构成。
调用变量的时候可以用"$(VALUE_LIST)"或者是 "${VALUE_LIST}"来替换,这就是变量的引用。

实例:

OBJ=main.o max.o
main:$(OBJ)
	gcc -o main $(OBJ)
main.o:main.c max.h
	gcc -c main.c
max.o:max.c max.h
	gcc -c max.c
clean:
	del *.o main.exe

知道了如何定义,下面我们来说一下 Makefile 的变量的四种基本赋值方式:
(1)简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
(2)递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
(3)条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值无效。
(4)追加赋值 ( += ) 原变量基础上用空格隔开的方式追加一个新值。
1.简单赋值

OBJ=main.o max.o
main:$(OBJ)
	gcc -o main $(OBJ)
main.o:main.c max.h
	gcc -c main.c
max.o:max.c max.h
	gcc -c max.c
clean:
	del *.o main.exe

x:=foo
y:=$(x)b
x:=new
.PHONY:test
test:
	@echo "y=>$(y)"
	@echo "x=>$(x)"

make运行,编译等结果
在这里插入图片描述
2.递归赋值

x=foo
y=$(x)b
x=new
.PHONY:test
test:
    @echo "y=>$(y)"
    @echo "x=>$(x)"

在 shell 命令行执行make test我们会看到:

y=>newb
x=>new

3.条件赋值

x:=foo
y:=$(x)b
x?=new
.PHONY:test
test:
    @echo "y=>$(y)"
    @echo "x=>$(x)"

在 shell 命令行执行make test我们会看到:

y=>foob
x=>foo

4.追加赋值

x:=foo
y:=$(x)b
x+=$(y)
.PHONY:test
test:
    @echo "y=>$(y)"
    @echo "x=>$(x)"

在 shell 命令行执行make test我们会看到:

y=>foob
x=>foo foob

不同的赋值方式会产生不同的结果,我们使用的时候应该根据具体的情况选择相应的赋值规则。

变量使用的范围很广,它可以出现在规则的模式中,也可以出现在规则的命令中或者是作为 Makefile 函数的参数来使用。总之,变量的使用在我们的 Makefile 编写中还是非常广泛的,可以说我们的 Makefile 中必不可少的东西。

Makefile通配符的使用_Makefile自动化变量

在这里插入图片描述
实例 1:

.PHONY:clean
clean:
    rm -rf *.o test   #通配符可以使用在规则的命令当中

实例 2:

test:*.c   #可以使用在规则中
    gcc -o test *.c

实例3:

OBJ=*.c  #通配符用在变量里
test:$(OBJ)
    gcc -o test $(OBJ)

Makefile自动化变量
      说自动化变量前,先说一下Makefile中的匹配符%,后面要用:
      Make命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是%。

比如,假定当前目录下有 f1.c 和 f2.c 两个源码文件,需要将它们编译为对应的目标文件。

%.o: %.c

等同于下面的写法。

f1.o: f1.c
f2.o: f2.c

使用匹配符%,可以将大量同类型的文件,只用一条规则就完成构建。
自动化变量可以理解为由 Makefile 自动产生的变量:
      在模式规则中,规则的目标和依赖的文件名代表了一类的文件。规则的命令是对所有这一类文件的描述。我们在 Makefile 中描述规则时,依赖文件和目标文件是变动的,显然在命令中不能出现具体的文件名称,否则模式规则将失去意义。

自动化变量说明
$@表示规则的目标文件名。
$<规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。
$?所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。
$^代表的是所有依赖文件列表,使用空格分隔,有去重的功能。
$+类似“$^”,但是它保留了依赖文件中重复出现的文件。
下面我们就自动化变量的使用举几个例子。

实例1:
      以上例代码为基础,我们编写test.c(main.c),test1.c(max.c),test2.c(max2.c)test.h四个文件.
在这里插入图片描述
Makefile文件老实写法与自动化变量写法

#很老实的写
test:test.o test1.o test2.o
	gcc -o test test.o test1.o test2.o
test.o:test.c
	gcc -c test.c -o test.o
test1.o:test1.c
	gcc -c test1.c -o test1.o
test2.o:test2.c
	gcc -c test2.c -o test2.o
clean:
	del *.o *.exe

shell中执行该项目产生test.exe可执行文件
在这里插入图片描述
      由些可以看出,如果老实写makefile文件,效率跟直接在shell中写编译执行c文件命令没有多大的区别,还是太麻烦了,

自动化变量写法

#自动化变量写法
test:test.o test1.o test2.o
	gcc -o $@ $^
test.o:test.c
	gcc -c $< -o $@
test1.o:test1.c
	gcc -c $< -o $@
test2.o:test2.c
	gcc -c $< -o $@
clean:
	del *.o *.exe

配合上%更简单的写法:

test:test.o test1.o test2.o
	gcc -o $@ $^
%.o:%.c
	gcc -c $< -o $@
clean:
	del *.o *.exe

在这里插入图片描述

Makefile目标文件搜索

            我们都知道一个工程文件中的源文件有很多,并且可能会存放工程目录下的不同子目录,所以按照之前的方式去编写 Makefile 会有问题。
前面的例子,源文件基本上都是存放在与 Makefile 相同的目录下。
只要依赖的文件存在,并且依赖规则没有问题,执行 make命令就没问题。
但是,如果需要的文件是存在于不同的路径下,在编译的时候要去怎么办呢?

使用目标文件搜索,搜索的方法的主要有两种:一般搜索VPATH和选择搜索vpath
VPATH的使用
在 Makefile 中可以这样写:

VPATH := src   #src是子目录名称

我们可以这样理解,把 src 的值赋值给变量 VPATH,所以在执行 make 的时候,make就会先从当前目录找目标,没找到就会继续从 src 目录下找。

当存在多个路径的时候我们可以这样写:

VPATH := src car

或者是

VPATH := src:car

例子:主要文件与子文件所在的目录在同一个目录下
在这里插入图片描述
       上图代码中,当make时,首先从当前目录下找所要的引入文件,如果没有,则从VPATH中定义的module和include目录中寻找。
注意:引入文件时,要写相对路径或绝对路径,如主程序test.c要引入test1.c或test2.c用到test.h文件,test.h它在test.c同目录下的include目录中。

#include<stdio.h>
#include<stdlib.h>
#include "./include/test.h"
 
int main(void)
{
    printf("The bigger one of 3 and 5 is%d\n", max(3, 5));
    system("pause");
    return 0;
}

vpath的使用
       我们可以这样理解:VPATH 是搜索路径下所有的文件,而 vpath 更像是添加了限制条件,会过滤出一部分再去寻找。所以VPATH效率比vpath效率低,能用vpath 就不去用VPATH。

具体用法:

vpath PATTERN DIRECTORIES 
vpath PATTERN
vpath

( PATTERN:可以理解为要寻找的条件,DIRECTORIES:寻找的路径 )

用法一,命令格式如下:

vpath test.c src

       可以这样理解,在 src 路径下搜索文件 test.c。

多路径的书写规则如下:

vpath test.c src car         
vpath test.c src : car

     多路径的用法其实和 VPATH 差不多,都是使用空格或者是冒号分隔开,
搜索路径的顺序是先 src 目录,然后是 car 目录。

用法二,命令格式如下:

vpath test.c

用法二的意思是清除符合文件 test.c 的搜索目录。

用法三,命令格式如下:

vpath

vpath 单独使的意思是清除所有已被设置的文件搜索路径。

     另外在使用 vpath 的时候,搜索的条件中可以包含模式字符“%”,这个符号的作用是匹配一个或者是多个字符,例如“%.c”表示搜索路径下所有的 .c 结尾的文件。如果搜索条件中没有包含“%" ,那么搜索的文件就是具体的文件名称。

vpath %.c moudle
vpath %.h include
test:test.o test1.o test2.o
	gcc -o $@ $^
%.o:%.c
	gcc -c $< -o $@
clean:
	del *.o *.exe

Makefile ifeq、ifneq、ifdef和ifndef(条件判断)

Makefile给我们提供了像C语言一样的条件判断的能力:

关键字功能
ifeq判断参数是否相等,相等为 true,不相等为 false。
ifneq判断参数是否不相等,不相等为 true,相等为 false。
ifdef判断是否有值,有值为 true,没有值为 false。
ifndef判断是否有值,没有值为 true,有值为 false。

ifeq 和 ifneq
--------条件判断的使用方式如下:

ifeq (ARG1, ARG2)

例子:

VPATH := module include
cc = gcc
test:test.o test1.o test2.o
	gcc -o test test.o test1.o test2.o
%.o:%.c
	gcc -c $< -o $@
test2:
ifeq ($(cc),gcc)
	@echo  yes
else
	@echo no
endif

clean:
	del *.o *.exe

执行make test2 回车,·@echo是shelll中输出内容,

PS C:\Users\Administrator\Desktop\workspaceC\project2> make test2
 yes
PS C:\Users\Administrator\Desktop\workspaceC\project2>

ifdef 和 ifndef
使用方式如下:

ifdef VARIABLE-NAME

它的主要功能是判断变量的值是不是为空,实例:

实例 :

bar =
foo = $(bar)
all:
ifdef foo
    @echo yes
else
    @echo  no
endif

实例 :

foo=
all:
ifdef foo
    @echo yes
else
    @echo  no
endif

注意:我们需要判断一个变量的值是否为空的时候需要使用“ifeq" 而不是“ifdef”。

Makefile常用字符串处理函数

    学习使用函数之前,先来看一下函数的语法结构。
函数的调用和变量的调用很像。
引用变量的格式为$(变量名),函数调用的格式如下:

$(<function> <arguments>)    或者是     ${<function> <arguments>}
   其中,function 是函数名,arguments 是函数的参数,参数之间要用逗号分隔开。
而参数和函数名之间使用空格分开。
   调用函数的时候要使用字符“$”,后面可以跟小括号也可以使用花括号。

本节主要讲的是字符串处理函数,这些都是我们经常使用到的函数,下面是对函数详细的介绍。

1. 模式字符串替换函数,函数使用格式如下:
   $(patsubst <pattern>,<replacement>,<text>)
   函数说明:函数功能是查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换。
返回值为替换后的新字符串。

实例:

OBJ=$(patsubst %.c,%.o, 1.c 2.c 3.c)
all:
    @echo $(OBJ)

执行 make all 命令,我们可以得到的值是 “1.o 2.o 3.o”,这些都是替换后的值。
2. 字符串替换函数,函数使用格式如下:

$(subst <from>,<to>,<text>)

函数说明:函数的功能是把字符串中的 form 替换成 to,返回值为替换后的新字符串。
实例:

OBJ=$(subst ee,EE,feet on the street)
all:
    @echo $(OBJ)  #@echo前面一定要按tap键,不然执行make all报错

执行 make all 命令,我们得到的值是“fEEt on the strEEt”。
3. 去空格函数,函数使用格式如下:

$(strip <string>)

函数说明:函数的功能是去掉字符串的开头和结尾的空格字符,并且将其中的多个连续的空格合并成为一个空格。返回值为去掉空格后的字符串。
实例:

OBJ=$(strip    a       b c)
all:
    @echo $(OBJ)    #@echo前面一定要按tap键,不然执行make all报错

执行完 make 之后,结果是“a b c”。
这个只是除去开头和结尾的空格字符,并且将字符串中的空格合并成为一个空格。
4. 查找字符串函数,函数使用格式如下:

$(findstring <find>,<in>)

函数说明:函数的功能是查找 in 中的 find ,如果我们查找的目标字符串存在。
返回值为目标字符串,如果不存在就返回空。
实例:

OBJ=$(findstring a,a b c)
all:
    @echo $(OBJ)

执行 make all 命令,得到的返回的结果就是 “a”。
5. 过滤函数,函数使用格式如下:

$(filter <pattern>,<text>)

函数说明:函数的功能是过滤出 text 中不符合模式 pattern 的字符串,可以有多个 pattern 。
返回值为过滤后的保留下来的符合模式的字符串。
实例:

OBJ=$(filter %.c %.o,1.c 2.o 3.s)
all:
    @echo $(OBJ)

执行 make 命令,我们得到的值是“1.c 2.o”。
7.反过滤函数,函数使用格式如下:

$(filter-out <pattern>,<text>)

函数说明:函数的功能是功能和 filter 函数正好相反,但是用法相同。
去除符合模式 pattern 的字符串,保留不符合的字符串。返回值是保留的字符串。
实例:

OBJ=$(filter-out 1.c 2.o ,1.o 2.c 3.s)
all:
    @echo $(OBJ)

执行 make all 命令,打印的结果是“3.s”。
7. 排序函数,函数使用格式如下:

$(sort <list>)

函数说明:函数的功能是将 中的单词排序(升序)。返回值为排列后的字符串。
实例:

OBJ=$(sort foo bar foo lost)
all:
    @echo $(OBJ)

执行 make 命令,我们得到的值是“bar foo lost”。
注意:sort会去除重复的字符串。
8. 取单词函数,函数使用格式如下:

$(word <n>,<text>)

函数说明:函数的功能是取出函数 中的第n个单词。返回值为我们取出的第 n 个单词。
实例:

OBJ=$(word 2,1.c 2.c 3.c)
all:
    @echo $(OBJ)

执行 make all 命令,我们得到的值是“2.c”。

Makefile常用文件名操作函数

    我们在编写 Makefile 的时候,很多情况下需要对文件名进行操作。
例如获取文件的路径,去除文件的路径,取出文件前缀或后缀等等。

Makefile 给我们提供了相应的函数去实现文件名的操作。

1. 取目录函数,函数使用格式如下:

$(dir <names>)

        函数说明:函数功能是从文件名序列 names 中取出目录部分 。
返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回“./”。
实例:

OBJ=$(dir src/foo.c hacks)
test:
	@echo $(OBJ)

        执行make test,输出如下,src/表示src目录下,./表示当前目录。上面代码写的目录和文件都是不存在的也可以输出。提取文件 foo.c 的路径是 “/src” 和文件 hacks 的路径 “./”。

PS C:\Users\Administrator\Desktop\workspaceC\makefile> make test
src/ ./

2. 取文件名函数,函数使用格式如下:

$(notdir <names>)

函数说明:函数功能是从文件名序列 names 中取出非目录的部分。
返回值为文件非目录的部分。
实例:

OBJ=$(notdir src/foo.c hacks)
all:
    @echo $(OBJ)

执行 make all 命令,我们可以得到的值是“foo.c hacks”。
3. 取后缀名函数,函数使用格式如下:

$(suffix <names>)

函数说明:函数功能是从文件名序列中 names 中取出各个文件的后缀名。
返回值为文件名序列 names 中的后缀序列,如果文件没有后缀名,则返回空字符串。
实例:

OBJ=$(suffix src/foo.c hacks)
all:
    @echo $(OBJ)

执行 make 命令,我们得到的值是“.c ”。文件 “hacks” 没有后缀名,所以返回的是空值。

4. 取文件基本名函数,函数使用格式如下:

$(basename <names>)

函数说明:函数的功能是从文件名序列 names 中取出各个文件名的前缀部分。
返回值为被取出来的文件的前缀名,如果文件没有前缀名则返回空的字符串。
实例:

OBJ=$(basename src/foo.c hacks)
all:
    @echo $(OBJ)

执行 make all 命令,我们可以得到值是“src/foo hacks”。获取的是文件的基本名,包含文件路径的部分。

5. 添加后缀名函数,函数使用格式如下:

$(addsuffix <suffix>,<names>)

函数说明:函数功能是把后缀 suffix 加到 names 中的每个单词后面。
返回值为添加上后缀的文件名序列。
实例:

OBJ=$(addsuffix .c,src/foo.c hacks)
all:
    @echo $(OBJ)

执行 make all后我们可以得到“sec/foo.c.c hack.c”。我们可以看到如果文件名存在后缀名,依然会加上。
6. 添加前缀名函数,函数使用格式如下:

$(addprefix <prefix>,<names>)

函数说明:函数的功能是把前缀 prefix 加到 names 中的每个单词的前面。
返回值为添加上前缀的文件名序列。
实例:

OBJ=$(addprefix src/, foo.c hacks)
all:
    @echo $(OBJ)

执行 make 命令,我们可以得到值是 “src/foo.c src/hacks” 。我们可以使用这个函数给我们的文件添加路径。

7. 连接函数,函数使用格式如下:

$(join <list1>,<list2>)

函数说明:函数功能是把 list2 中的单词对应的拼接到 list1 的后面。
如果 list1 的单词要比 list2的多,那么,list1 中多出来的单词将保持原样,
如果 list1 中的单词要比 list2 中的单词少,那么 list2 中多出来的单词将保持原样。
返回值为拼接好的字符串。
实例:

OBJ=$(join src car,abc zxc qwe)
all:
    @echo $(OBJ)

执行 make 命令,我们可以得到的值是“srcabc carzxc qwe”。

8. 获取匹配模式文件名函数,命令使用格式如下:

$(wildcard PATTERN)

函数说明:函数功能是列出当前目录下所有符合模式的 PATTERN 格式的文件名。
返回值为空格分隔并且存在当前目录下的所有符合模式 PATTERN 的文件名。
实例:

OBJ=$(wildcard *.c  *.h)
all:
    @echo $(OBJ)

执行 make all 命令,可以得到当前函数下所有的 ".c " 和 “.h” 结尾的文件。
这个函数通常跟的通配符 “*” 连用,使用在依赖规则的描述的时候被展开。

Makefile中的其它常用函数

1)循环函数

$(foreach <var>,<list>,<text>)

函数功能是:把参数中的单词逐一取出放到参数所指定的变量中,然后再执行 所包含的表达式。

每一次 会返回一个字符串,循环过程中, 的返所返回的每个字符串会以空格分割,最后当整个循环结束的时候, 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。

实例:

names:=a b c d
files:=$(foreach n,$(names),$(n).o)
all:
	@echo $(files)

执行 make 命令,我们得到的值是“a.o b.o c.o d.o”。

注意:foreach 中的 参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不再作用。
2)分支函数

$(if <condition>,<then-part>)(if<condition>,<then-part>,<else-part>)

if函数的返回值是:
如果condition为真(非空字符串),那么then-part会是整个函数的返回值。
如果condition为假(空字符串),那么else-part将会是这个函数的返回值。
此时如果else-part没有被定义,那么整个函数返回空字串符。
所以,then-part和else-part只会有一个被计算。

实例:

OBJ:=foo.c
OBJ:=$(if $(OBJ),$(OBJ),main.c)
test:
	@echo $(OBJ)

执行 make test 命令我们可以得到函数的值是 foo.c,
如果变量 OBJ 的值为空的话,我们得到的 OBJ 的值就是main.c。
3)call 函数

$(call <expression>,<parm1>,<parm2>,<parm3>,...)

我们可以用来写一个非常复杂的表达式expression,这个表达式中,我们可以包含很多的参数,然后你可以用 call 函数来向这个表达式传递参数。

当 make 执行这个函数的时候,expression参数中的变量 ( 1 ) 、 (1)、 (1)(2)、$(3)等,会被参数parm1,parm2,parm3依次取代。而expression的返回值就是 call 函数的返回值。

实例 1:

reverse = $(1) $(2)
foo = $(call reverse,a,b)
all:
      @echo $(foo)

那么,foo 的值就是“a b”。当然,参数的次序可以是自定义的,不一定是顺序的,

实例 2:

reverse = $(2) $(1)
foo = $(call reverse,a,b)
all:
      @echo $(foo)

此时的 foo 的值就是“b a”。
4)origin 函数

$(origin <variable>)

origin 函数不像其他的函数,它并不操作变量的值,它只是告诉你这个变量是哪里来的。

注意: variable 是变量的名字,不应该是引用,所以最好不要在 variable 中使用“$”字符。
origin 函数会返回值来告诉你这个变量的“出生情况”。

下面是origin函数返回值:
(1)“undefined”:如果从来没有定义过,函数将返回这个值。
(2)“default”:如果是一个默认的定义。
(3)“environment”:如果是一个环境变量。
(4)“file”:如果这个变量被定义在Makefile中,将会返回这个值。
(5)“command line”:如果这个变量是被命令执行来的,在命令行中输入“make O=命令”。
(6)“override”:如果是被override关键字重新定义的。
(7)“automatic”:如果是一个命令运行中的自动化变量。
这些信息对于我们编写 Makefile 是非常有用的。
例子:

#origin函数,判断变量的来源,下面由于abc变量没有定义,所以输出undefined
#OBJ = $(origin abc)
#test:
#	@echo $(OBJ)

#origin函数,判断变量的来源,CC是make工具自定的变量,所以输出default
#OBJ = $(origin CC)
#test:
#	@echo $(OBJ)

#origin函数,判断变量的来源,ex是在Makefile中定义的变理,所以输出file
#ex = a
#OBJ = $(origin ex)
#test:
#	@echo $(OBJ)

#origin函数,判断变量的来源,OS是在windows中定义的环境变量,所以输出“environment"
OBJ = $(origin OS)
test:
	@echo $(OBJ)

判断变量的来源在特殊场合是非常有用的

ifdef bletch
    ifeq "$(origin bletch)" "environment"
        bletch = barf,gag,etc
    endif
endif

Makefile include文件包含

      Makefile 和c源码文件一样,也可以包含其他的文件,关键字也是 “include”,
当 make 读取到 “include” 关键字的时候,会暂停读取当前的 Makefile,
而是去读 “include” 包含的文件,读取结束后再继读取当前的 Makefile 文件。

include <filenames>

include 通常使用在以下的场合:
(1)在一个工程文件中,每一个模块都有一个独立的 Makefile 来描述它的重建规则。
将共同使用的变量或者模式规则定义在一个单独文件中,需要的时候用 “include” 包含这个文件。

(2)当根据源文件自动产生依赖文件时,我们可以将自动产生的依赖关系保存在另一个文件中。
然后在 Makefile 中包含这个文件。

注意:如果使用 “include” 包含文件的时候,指定的文件不是文件的绝对路径或者是为当前文件下没有这个文件,make 会根据文件名会在以下几个路径中去找,首先我们在执行 make 命令的时候可以加入选项 “-I” 或 “–include-dir” 后面添加上指定的路径,如果文件存在就会被使用,如果文件不存在将会在其他的几个路径中搜索:“usr/gnu/include”、“usr/local/include” 和 “usr/include”。

如果在上面的路径没有找到 “include” 指定的文件,make 将会提示一个文件没有找到的警示提示,但是不会退出,而是继续执行 Makefile 的后续的内容。当完成读取整个 Makefile 后,make 将试图使用规则来创建通过 “include” 指定但不存在的文件。当不能创建的时候,文件将会报错退出。
使用时,通常用 “-include” 来代替 “include” 来忽略文件不存在或者是无法创建的错误提示,使用格式如下:

-include <filename>

使用方法和 “include” 的使用方法相同。

这两种方式之间的区别:
(1)使用 “include ” ,make 在处理程序的时候,文件列表中的任意一个文件不存在的时候
或者是没有规则去创建这个文件的时候,make 程序将会提示错误退出。
(2)使用 “-include ”,当包含的文件不存在或者是没有规则去创建它的时候,
make 将会继续执行程序,只有不能完成终极目标重建的时候我们的程序才会提示错误退出。
例子:
在这里插入图片描述
Makefile

include mk001
clean:
	del *.o *.exe

mk001

#下面一行代码功能:先获取所有当前目录下的*.c文件,再获取文件名如test1.c,再把*.c改为*.o文件
obj = $(foreach x,$(basename $(wildcard *.c)),$(x).o)
test:$(obj)
	gcc -o $@ $^
%.o:%.c
	gcc -c $< -o $@

执行make命令,
在这里插入图片描述

Makefile嵌套执行make

      我们都知道在一个大的工程文件中,不同的文件按照功能被划分到不同的模块中,也就说很多的源文件被放置在了不同的目录下。

      每个模块可能都会有自己的编译顺序和规则,如果在一个 Makefile 文件中描述所有模块的编译规则,就会很乱,执行时也会不方便,所以就需要在不同的模块中分别对它们的规则进行描述,也就是每一个模块都编写一个 Makefile 文件,这样不仅方便管理,而且可以迅速发现模块中的问题。

这样我们只需要控制其他模块中的 Makefile 就可以实现总体的控制,这就是 make 的嵌套执行。

如何来使用呢?举例说明如下:

subproject:
    @cd subdir && $(MAKE)

      这个例子可以这样来理解,在当前目录下有一个目录文件 subdir 和一个 Makefile 文件,子目录 subdir 文件下还有一个 Makefile 文件,这个文件是用来描述这个子目录文件的编译规则。

使用时只需要在最外层的目录中执行 make 命令,当命令执行到上述的规则时,程序会进入到子目录中执行 make。这就是嵌套执行 make,我们把最外层的 Makefile 称为是总控 Makefile。

上述的规则也可以换成另外一种写法:

subproject:
    $(MAKE) -C subdir

例:
在这里插入图片描述
主Makefile:注意:@cd subproject && $(MAKE),clean功能可删除所有(包括子模块的)的o和exe文件

obj = $(foreach x,$(basename $(wildcard *.c)),$(x).o)
test:$(obj)
	gcc -o $@ $^
	@cd subproject && $(MAKE)
%.o:%.c
	gcc -c $< -o $@
clean:
	del /s /f /q *.o *.exe

子Makefile

obj = $(foreach x,$(basename $(wildcard *.c)),$(x).o)
test:$(obj)
	gcc -o $@ $^
%.o:%.c
	gcc -c $< -o $@
clean:
	del *.o *.exe

export的使用
   使用 make 嵌套执行的时候,变量是否传递也是我们需要注意的。
如果需要变量的传递,那么可以这样来使用:

export variable = value
<variable>是变量的名字,不需要使用 "$" 这个字符。

如果所有的变量都需要传递,那么只需要使用 “export” 就可以,不需要添加变量的名字。

Makefile 中还有两个变量不管是不是使用关键字 “export” 声明,它们总会传递到下层的 Makefile 中。
这两个变量分别是 SHELL 和 MAKEFLAGS,特别是 MAKEFLAGS 变量,包含了 make 的参数信息。

make 命令中有几个参数选项并不传递,它们是:"-C"、"-f"、"-o"、"-h" 和 "-W"。
如果我们不想传递MAKEFLAGS变量的值,在 Makefile 中可以这样来写:

subsystem:
    cd subdir && $(MAKE) MAKEFLAGS=

Makefile变量的高级用法

1)变量的替换引用
实例:

foo:=a.c b.c d.c
obj:=$(foo:.c=.o)
test:
    @echo $(obj)

      这段代码实现的功能是字符串的后缀名的替换,把变量 foo 中所有的以 .c 结尾的字符串全部替换成 .o 结尾的字符串。

注意:括号中的变量使用的是变量名而不是变量名的引用,变量名的后面要使用冒号和参数选项分开,表达式中间不能使用空格。

   上面的例子我们可以换一种更加通用的方式来写,代码展示如下:

foo:=a.c b.c d.c
obj:=$(foo:%.c=%.o)
All:
    @echo $(obj)

执行make all

PS C:\Users\Administrator\Desktop\workspaceC\project\subproject> make all
a.o b.o d.o

      为什么这种方式比第一种方式更加实用呢?我们在实际使用的过程中,我们对变量值的操作不只是修改其中的一个部分,甚至是改变其中的多个,那么第一种方式就不能实现了。我们来看一下这种情况:

foo:=a123c a1234c a12345c
obj:=$(foo:a%c=x%y)
All:
    @echo $(obj)

例:把上面项目project的Makefile代码修改一下

#obj = $(foreach x,$(basename $(wildcard *.c)),$(x).o)
names :=$(wildcard *.c)
obj :=$(names:%.c=%.o)
test:$(obj)
	gcc -o $@ $^
	@cd subproject && $(MAKE)
%.o:%.c
	gcc -c $< -o $@
clean:
	del /s /f /q *.o *.exe

2)变量的嵌套使用
      我们可以在一个变量的赋值中引用其他的变量,并且引用变量的数量和和次数是不限制的。
实例 1:

foo:=test
var:=$(foo)
All:
    @echo $(var)

实例 2:

foo=bar
bar=test
var:=$($(foo))
All:
    @echo $(var)

实例 4:

first_pass=hello
bar=first
var:=$($(bar)_pass)
all:
    @echo $(var)

      在命令行执行 make 我们可以得到 var 的值是 hello。这是变量嵌套引用的时候可以包含其它字符的使用情况。

实例 5:

first_pass=hello
bar=first
foo=pass
var:=$($(bar)_$(foo))
all:
    @echo $(var)

      这个实例跟上面实例的运行结果是一样的。我们可以看到这个实例中使用了两个变量的引用还有其它的字符。
建议你的实际编写 Makefile 时要尽量避免这种复杂的用法。

静态库与动态库

基本概念

我们知道程序编译一般需经预处理、编译、 汇编和链接几个步骤。
在我们的应用中,有一些公共代码是需要反复使用,
为了提高代码的复用,简化开发,就把这些代码编译为“库”文件。

静态库:在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中。
这种库称为静态库,一般的后缀为.a或.lib,其特点是可执行文件中包含了库代码的一份完整拷贝;
缺点就是被多次使用就会有多份冗余拷贝。

动态库:是一个包含可由多个程序同时使用的代码和数据的库。
Windows下动态库为.dll后缀,在linux在为.so后缀。
在程序运行的时候,将库加载到程序中,运行的时候需要外部函数库。

一. 静态库的生成和使用

下面通过一个简单的小栗子来介绍库函数怎么生成和使用.
1.库函数的源码 hello.c

#include "stdio.h"

int hello(void){

    printf("hello lib");  
  return 0;
}
  1. 库函数的头文件 hello.h
#ifndef __HELLO_H
#define __HELLO_H
int hello(void);
#endif

库文件的头文件是库文件的目录,因为库文件是保护的,看不到里面的源码,所以把函数接口通过头文件来让人调用
这样就实现了接口,也保护了源码

3.编译静态库函数
   3.1 将hello.c 编译成目标文件 生成hello.o文件

gcc -c hello.c

   3.2 将.o文件打包成静态库 生成 libhello.a库文件

ar -cr libhello.a hello.o

4 使用静态库,因为静态库是在编译的时候一起打包进程序的,所以如果编译的时候没有静态库文件,则无法编译
   4.1 main.c 写一个main函数来调用库函数

#include "hello.h"    //引入库函数的头文件,这样才能找到函数声明

int main{
  hello();        //调用库函数
}

正常编译的时候是没办法通过的. 因为编译器找不到 hello() 的实现代码.

所以在编译的时候要加入库引用

gcc main.c -L. -lhello 

-L<路径> 引用自定义库的路径,如果调用系统库就不用-L '.'表示当前文件夹
-lxxxx 这里libhello.a 只要写hello 就可以   //小写的L
   4.2 直接可以执行,因为库函数已经被编译进去了

二.动态库的编译及使用

同样的hello.c,main.c,hello.h 源码

1.生成.o文件

gcc -c -fPIC  hello.c //如果这里没有加-fPIC 下一步就会提示你重新用 -fPIC 编译

    -fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

  1. 编译成动态库
gcc -shared -fPIC -o libhello.so hello.o

-shared 是生成动态库

  1. 使用动态库 和静态库一样
gcc main.c -L. -lhello -o a.out  //实验证明大小的L不可以少
  1. 执行用动态库编译的程序没办法直接执行
./a.out

在读取共享库libhello.so的时候出错,没有找到该文件
因为动态库程序会默认在 /lib 或者 /usr/lib的路径下寻找, 所以

解决的办法有3个:
1.将.so 文件拷贝到 /usr/lib/文件夹下面

sudo cp ./libhello.so /usr/lib
./a.out

2.添加PATH环境变量
export LD_LIBRARY_PATH=<动态库所在的绝对路径>
在这里插入图片描述

./a.out

3.修改配置脚本
将动态库所在的路径加到 /etc/ld.so.conf 文件里
vim /etc/ld.so.conf
在这里插入图片描述

添加后刷新
/sbin/ldconfig

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_33406021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值