Makefile多文件编程+Makefile制作Linux动态库和静态库【实例化、简单易懂】

目录

一、多文件编程

二、Makefile 简单实例

2.1 简介

2.2 格式

2.3 实践

三、使用Makefile制作动态库、静态库

3.1 Linux下的库(动态库、静态库)简介

3.1.1 什么是库

3.1.2 库的种类

3.1.3 库存在的意义

3.1.4 在linux下如何制作库文件

3.1.5 查看可执行程序依赖的库(ldd命令)

3.1.6 如何让系找到库

3.2 Makefile制作静态库

3.3 Makefile 制作动态库

3.4 Makefile的改进(以动态库为例)

3.4.1 基本替换和赋值

3.4.2 对3.2中静态库的Makefile改进


本文用简单的实践方式,介绍下Makefile多文件编程,以及Makefile制作Linux动态库、静态库的方法。

 

一、多文件编程

为了代码工程的具有较好的可维护性、可读性以及低耦合的要求。一般情况下,工程都会以多文件方式存在,将不同功能的方法函数写在不同的源文件中,供其它文件调用。

写一个简单的例子:

比如现在,我们要计算两个数的加法,和两个数的乘法。

那么,“加法”的功能函数实现单独写在add.c源文件中、“乘法”的功能函数实现单独写在mul.c源文件中。另外,源文件中所有的函数声明,应写在源文件的同名头文件中,在这里,add.c中有一个函数add(),其函数声明写在add.h中;mul.c中有一个函数mul(),其函数声明写在mul.h中,供其它源文件调用。

然后,写一个最简单的main函数,加入两者的头文件,实现对两个功能函数的调用。

基于此,我们现在得到五个文件:add.h、add.c、mul.h、mul.c、main.c。大概,就是这么个关系吧:

下面,使用mkdir命令新建一个目录,然后编写一下这五个文件,五个文件的代码,很简单:

//add.h文件
int add(const int a, const int b);


//add.c文件
#include<stdio.h>
#include"add.h"

int add(const int a, const int b)
{
	return a + b;
}

//mul.h文件
int mul(const int a, const int b);

//mul.c文件
#include<stdio.h>
#include"mul.h"

int mul(const int a, const int b)
{
	return a * b;
}


//main.c文件
#include<stdio.h>
#include"mul.h"
#include"add.h"

int main(int argc, char* argv[])
{
	int a = 10;
	int b = 20;

	printf("add:%d\tmul:%d\n", add(a,b), mul(a,b));
}

完成后,目录下,应该是这样:

我们通过以下指令,先编译生成各自的目标文件(add.o, mul.o, main.o)。然后进行链接,生成可执行文件main,并执行。

运行正确。(这里gcc编译的知识,如果不会要补充下呦,gcc -E -s -c -o参数分别代表什么意思~)

二、Makefile 简单实例

2.1 简介

Makefile主要用来在一个工程中,管理多文件的编译。

如第一张最后的编译过程,我们写了三个gcc -c命令,分别编译生成了三个.o目标文件,然后又通过gcc,将他们链接,最后生成了可执行文件。如果你改了add.c的函数实现,那么,以上编译、链接步骤就要重新手动执行一次。如果是大型的工程,改其中一个源文件,可不就要根据编译逻辑,敲良久~

Makefile就是你写一个编译规则,这个编译规则用来规定工程中所有源文件的编译顺序,那么以后你改动任何一个源文件,规则是不变的,只需要执行make一下,他就会按照预定的规则将文件重新编译。即便是添加删除文件,只需要在对应的规则上稍作修改即可,一劳永逸,节省时间。

https://www.jianshu.com/p/29eb94f029c7这里介绍了有关Makefile的基本原理,可以看一下。

2.2 格式

Makefile的格式很简单,也很好理解:

******

目标: 依赖

(tab)规则

******

“目标” 是需要生成的目标文件;“依赖”是生成该目标所需的一些文件;“规则”是由依赖文件生成目标文件的手段。用一句话概括,其实就是:把“依赖”根据“规则”编译成“目标”

要注意:每条“规则”必须以 tab 开头,不能使用空格等。

2.3 实践

在第一章的代码目录下,编写一个Makefile,管理工程:

main: main.o add.o mul.o
	gcc main.o add.o mul.o -o main
main.o: main.c
	gcc main.c -c
add.o: add.c
	gcc add.c -c
mul.o: mul.c
	gcc mul.c -c

#.PHONY:clear表示clear是一个伪目标.
#如果不加.PHONY,当前目录下由clear的文件就会执行不了,且提示该文件是最新的.
#rm前边多减号表示:不管出现什么问题,都要做后边多事情
.PHONY:clear
clear:
	rm *.o

保存,当前目录含有六个文件:

在当前目录下输入make,执行Makefile编译规则,可见,生成了main执行文件,并且执行后,结果正确:

另外,在我们的Makefile中,有一个clear目标,并且声明了它为.PHONY伪目标。且他没有依赖文件,只有规则,规则是删除所有.o文件。如果我们想这样做的话,直接在当前目录下,输入make+目标名即可,即“make clear”:

三、使用Makefile制作动态库、静态库

3.1 Linux下的库(动态库、静态库)简介

本小节对Linux下的库做了基本的概要总结,摘录博文地址:【C语言】Linux下动态库和静态库详解

3.1.1 什么是库

在windows平台和linux平台下都大量存在着库。

本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。

 

3.1.2 库的种类

Linux下的库有两种:静态库和动态库(共享库)。

 

静态库的后缀是.a;

动态库的后缀是.so

 

静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称

动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号。当然,主、副版本号也可以不写。

 

二者的不同点在于:代码被载入的时刻不同。

静态库的代码在编译过程中已经被载入可执行程序,因此体积较大

动态库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小

 

3.1.3 库存在的意义

库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。

现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

 

3.1.4 在linux下如何制作库文件

静态库制作分两步:

Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表

Step 2.ar命令将很多.o转换成.a,成为静态库

动态库的制作由gcc加特定参数编译产生。

 

3.1.5 查看可执行程序依赖的库(ldd命令)

例如# ldd /bin/lnlibc.so.6

=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2

=> /lib/ld- linux.so.2 (0×40000000)

可以看到ln命令依赖于libc库和ld-linux库

 

3.1.6 如何让系找到库

如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。

如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:

1.编辑/etc/ld.so.conf文件,加入库文件所在目录的路径

2.运行ldconfig,该命令会重建/etc/ld.so.cache文件

 

3.2 Makefile制作静态库

我们在第二章中,用两个源文件add.c、mul.c分别实现了两个数的相加和相乘操作,他们有一个特点,都是数学运算,我们可以把这两个源文件,添加到一个库中,成为一个math库,这个库,就可以用在任何需要计算两数相加、两数相乘的地方。

本节先介绍math静态库(libmath.a)的制作和使用方法。

重写上一章的Makefile:

a.out: main.c libmath.a
	gcc main.c -L . -l math -o a.out 
#上边这一行: -L参数:指定目录; -l参数:要链接的库,"-l math"可以连写成"-lmath";
#这里注意,math库不要写"labmath.a"

libmath.a: add.o mul.o
	ar -r libmath.a add.o mul.o
#上边这一行:将两个.o文件加入math库中

add.o: add.c
	gcc add.c -c
mul.o: mul.c
	gcc mul.c -c

#.PHONY:clear表示clear是一个伪目标.
#如果不加.PHONY,当前目录下由clear的文件就会执行不了,且提示该文件是最新的.
#rm前边多减号表示:不管出现什么问题,都要做后边多事情
.PHONY:clear
clear:
	rm *.o a.out libmath.a

注释应该写的很清楚了,主要过程大概梳理为:编译add.c和mul.c生成目标文件add.o和mul.o,将add.o和mul.o添加到libmath.a库中。最后,让main.c在编译过程中,链接libmath.a静态库,生成最后可执行文件a.out。

执行下make

 

3.3 Makefile 制作动态库

本节介绍math动态库(libmath.so)的制作和使用方法。

重写Makefile:

a.out: main.c libmath.so
	gcc main.c -L . -l math -o a.out 
#上边这一行: -L参数:指定目录; -l参数:要链接的库,"-l math"可以连写成"-lmath";
#这里注意,math库不要写"labmath.so"

libmath.so: add.o mul.o
	gcc -shared add.o mul.o -o libmath.so
#	ar -r libmath.a add.o mul.o  对比一下静态库的方法.
#	-shared:表示输出结果是共享库类型

add.o: add.c
	gcc -c -fpic add.c
mul.o: mul.c
	gcc -c -fpic mul.c
#上边这几行,添加了参数"-fpic",表示将源文件编译成带有PIC标志的目标文件.


.PHONY:clear
clear:
	rm *.o a.out libmath.so


#注意,动态库执行时,要把生成多动态库文件移动到/lib目录下!

解释都在上述的Makefile文件中了,不再累述了~

make一下后不能马上执行main可执行文件,因为这时候还没有把动态库放到系统能找到的地方。

我们需要把make生成的动态库libmath.so拷贝到/lib目录,在执行a.out的时候,系统会去/lib中查找相应的动态库,进行链接。其它方法,请参考本文3.2.6小结。

操作和执行结果:

 

3.4 Makefile的改进(以动态库为例)

前面我们介绍了用Makefile编译多文件、制作动态库和静态库的方法。

其实,这些Makefile的使用是很初级化的,为了让Makefile更好用,学问还是很深的,这里给出一些基本的概念和改进思想。这样看完,应该算是直到Makefile是个什么东西了,以后做项目需要的时候,可以再逐步深入即可。

3.4.1 基本替换和赋值

(1) 变量:
    OJBC:一般用来代表.o文件
    CC:代表编译

(2) 自动变量:
    $@:表示目标文件
    $%:如果目标是函数库文件,那么$%表示库文件的成员.  比如静态库f.a,$%表示他的成员m.o sort.o.
    $+:所有的依赖文件,不去重复
    $^:所有的依赖文件,去重复
    $<:第一个依赖文件
    $?:所有的依赖文件,空格隔开,这些依赖文件比目标还要新(修改时间比目标晚)


(3) 赋值:
    :=  简单赋值,常规理解的赋值,只对当前语句的变量有效
        (中间被重新赋值了,前边多不会改变,后边的会改变为新值)
    =    递归赋值,赋值语句可能影响多个变量,所有目标变量相关的其它变量都受影响
        (最后被赋值是什么,全文引用该变量的地方都变成什么)
    ?=    条件赋值,如果变量之前未定义,使用符号中的值定义变量,若已定义(之前被赋值过),该赋值语句无效
    +=    追加复制,原变量用空格隔开的方式追加一个新值

 

3.4.2 对3.2中静态库的Makefile改进

CC := gcc

OJBC = add.o 
OJBC += mul.o

TARGET := a.out

MAIN_C = main.c

#以下是动态库相关的变量  这里使用递归赋值,math头文件在任何一处改变,全文件变量随之改变.
#libmath.a
A_LIB_NAME = math
A_LIB = lib$(A_LIB_NAME).a   



$(TARGET): $(MAIN_C) $(A_LIB)
	$(CC) $< -L . -l $(A_LIB_NAME) -o $(TARGET)

$(A_LIB): $(OJBC)
	ar -r $@ $^

%.o:%.c
	$(CC) $< -c

.PHONY:clear
clear:
	rm $(OJBC) $(TARGET) $(A_LIB)



#原来的写法:
#a.out: main.c libmath.a
#	gcc main.c -L . -l math -o a.out 
#
#libmath.a: add.o mul.o
#	ar -r libmath.a add.o mul.o
#
#add.o: add.c
#	gcc add.c -c
#mul.o: mul.c
#	gcc mul.c -c
#
#.PHONY:clear
#clear:
#	rm *.o a.out libmath.a

为什么要用定义CC变量($(CC))呢?为了交叉编译好用,比如,如果想在Ubuntu上交叉编译一个arm架构的应用程序,使之能在arm上运行,那么就需要将makefile中所有的gcc改成 arm-none-linux-gnueabi-gcc交叉编译工具链,为了到时候方便改动,可以在文件启示位置,将编译工具设为变量。其它很多变量也是这个问题,为了方便改名、替换。

为什么要使用%.o、%.c代替文件?很明显,很多相同的.c文件编译成.o文件,他们的规则都是一样的,比如你想在math静态库中增加“减法”方法,是不是makefile中,不用再增加gcc -c生成目标文件的操作了。

为什么使用${OJBC}使用递归赋值?当你想增加新的文件,比如math静态库中增加“减法”,在makefile中,只需要用+=追加赋值的方法,就可以让全文的${OJBC}都改变,方便增删调试。另外还可以用与Makefile的if..else..条件语句中,实现不同情况的变量差异化。

自动变量是很常见的,主要用在“规则”中,对“目标”和“依赖”进行一种符号化的引用(此说法可能不太正规哈,主要是表其意思)。也是一样的,为了方便,为了增减文件的时候,可以更少的更改Makefile,更好的一劳永逸

当然,Makefile还有很多东西可以补充,这里只带大家入门,应对自己写的简单的小工程差不多了,公司中,大的工程一般会有已经写好的Makefile,自己添加代码和文件基本也不会把Makefile结构都改了。总的来说,Makefile看懂,知道思想,用的时候知道查即可吧。

个人认为,没有统一的模板,只有统一的思想~。没有说非得用自动变量或怎么样就好、主要目的是适合自己的工程。

 

  • 14
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值