【Linux】动静态库

outlier.cc 2024-10-01 11:37:30 阅读 70

目录

1. 静态库1.1 生成静态库1.2 静态链接1.2.1 带选项编译链接1.2.2 安装库到系统库路径1.2.3 系统库路径下建立软链接

2. 动态库2.1 生成动态库2.2 动态链接2.3 动态库的加载方式2.3.1 拷贝到系统默认的库路径2.3.2 建立软链接2.3.3 环境变量法2.3.3 建立动态库路径配置文件

2.4 动静态库的区别

在 linux 系统中,形如 libXXX.a 的文件称为静态库,对应是的静态链接;形如 libYYY.so 为动态库,对应的是动态链接,也是 gcc/g++ 的默认编译行为。但是对于动静态库 和 动静态链接,远远不只这些。

1. 静态库

1.1 生成静态库

<code>// mymath.h 文件

#pragma once

#include<stdio.h>

extern int myerrno;

int add(int x, int y);

int sub(int x, int y);

int mul(int x, int y);

int div(int x, int y);

// mymath.c 文件

#include "mymath.h"

int myerrno = 0;

int add(int x, int y) { return x + y; }

int sub(int x, int y) { return x - y; }

int mul(int x, int y) { return x * y; }

int div(int x, int y) {

if(y == 0){

myerrno = 1;

return -1;

}

return x / y;

}

将我们自己编写的方法提供给其它文件,有两种途径:

提供源代码,在其它文件下包含该头文件,编译时,将方法实现一同编译把源代码打包成库,最终提供库 + 头文件‘

lib=libmymath.a # 静态库的名称

# ar --- 生成静态库(把所有 .o 文件打包形成 .a 文件)

# -rc --- replace 和 create 的意思,如果目标文件存在则替换,不存在则创建。

$(lib):mymath.o

ar -rc $@ $^

# 我们 gcc 多文件编译时,就是把每个文件编译为 .o 文件,最后链接起来形成可执行程序

# 而所谓静态链接,无非就是先将源代码都编译为 .o 文件,把所有 .o 文件打包,命令为形如 libXXX.a 的文件

# 将来只需要将我们的 main.c 之类的源文件编译为 .o 文件,然后与 libXXX.a 结合,这就是静态链接!

mymath.o:mymath.c

gcc -c $^

.PHONY:clean

clean:

rm -rf *.o *.a lib

# 发布库

.PHONY:output

output:

mkdir -p lib/include

mkdir -p lib/mymathlib

cp *.h lib/include

cp *.a lib/mymathlib

在这里插入图片描述

这是因为找不到头文件,编译时,编译器会在系统头文件 和 当前路径下查找头文件,但这两个地方都没有 mymath.h。

1.2 静态链接

1.2.1 带选项编译链接

<code>gcc main.c -I ./lib/include/ -L./lib/mymathlib/ -lmymath

// -I 指定头文件所在目录

// -L 指定方法的实现所在目录

// -l 指定方法的实现所在目录中具体的库文件(一般不带空格)

// 库的文件名为 libmymath.h,但库的真实名字为 mymath(即去除lib前缀和文件格式后缀)

在这里插入图片描述

在这里插入图片描述

关于 gcc/g++ 的默认编译行为都是动态链接的,但是当我们没有动态库,只有静态库的时候,即便编译选项不带 <code>-static ,也只能是静态链接(因为只能静态链接啊,没有动态库啊,至少在编译时,需要用到的库,是静态链接的)。也可以理解为 -static 选项只是一个给编译器建议性的选项,在有相应的静态库时,编译器会听取建议。

1.2.2 安装库到系统库路径

静态链接也可以不用在编译时带上 -I-L 选项,如果直接把库文件拷贝到系统路径下,那么也可以实现编译时的静态链接,但是依旧需要带上 -l 选项指明具体要链接的库文件。(该做法一般不推荐)

在这里插入图片描述

1.2.3 系统库路径下建立软链接

<code>#include "myinc/mymath.h" // 建立了软链接后,头文件指向软链接即可

int main()

{

int ret = div(10, 0);

printf("10 / 0 = %d, errno = %d\n", ret, myerrno);

return 0;

}

在这里插入图片描述

在这里插入图片描述

但是无论是哪种链接方式,都需要在编译时指明特定的库文件,要不然编译器无法得知使用哪些静态库进行链接。

软链接的其中一个应用场景就是如此,可以在系统路径下通过建立软链接,快速定位各种库文件,而不再需要将整个库拷贝到系统路径下。


2. 动态

2.1 生成动态库

<code>// myprint.h

#pragma once

#include<stdio.h>

void Print();

// myprint.c

#include "myprint.h"

void Print()

{

printf("This is a dynamic-linking test!\n");

printf("This is a dynamic-linking test!\n");

printf("This is a dynamic-linking test!\n");

}

// mylog.h

#pragma once

#include<stdio.h>

void Log(const char* msg);

// myprint.c

#include "mylog.h"

void Log(const char* msg) { printf("Warning: %s\n", msg); }

生成动态库的核心思想与静态库是一致的,都是把各源文件先编译成 .o 文件,然后把所有 .o 文件打包,最后用户把自己的源文件编译为 .o 文件,再与库文件进行链接就行了。因此在链接之前,无论动静态库,在链接之前都需要先编译为 .o 文件。

静态库是通过命令 rc 打包生成的,但动态库并不直接通过系统指令来打包,动态库的生成一系列工作是内嵌在 gcc/g++ 编译器当中,可以理解动态库/动态链接与编译器的关系是最亲近的,这也就能够进一步理解为什么编译器的默认链接都是动态链接了。

shared: 表示生成共享库格式fPIC:产生位置无关码(position independent code)库名规则:libxxx.so

gcc -fPIC -c mylog.c# 先带上 -fPIC 生成 .o 文件

gcc -fPIC -c myprint.c

gcc -shared -o libmymethod.so *.o# 正常 -o 选项编译为生成可执行程序,加上 -shared 指明不直接生成可执行,而是生成一个共享库

在这里插入图片描述

静态库提供源代码的(二进制文件数据),当用户的可执行程序使用到静态库的数据时,把静态库的源代码拷贝到用户的可执行程序,拷贝完成后,用户的源文件编译就全部完成,跟静态库就再也没关系了,而将来可执行程序运行起来变为进程,静态库是不需要被加载到内存的,因为需要的静态库的数据,已经拷贝到可执行程序里面了。

动态库它并不像静态库一样,直接拷贝的形式,当可执行程序执行时,需要访问动态库的数据,就需要跳转到动态库去执行,动态库要被执行,就注定了要被加载到内存中。有了可执行权限作为前提,库文件才能快速的被系统加载到内存中(不排除没有 x 权限的文件也会被系统加载)。总而言之,给动态库带上 x 权限,也是一种 “动态库需要被执行” 的含义,虽然动态库里面没有 main 函数,但是它提供的方式是将来用户的可执行程序可能要访问的,也算是用户可执行程序的一部分了,因此也算是用户的可执行程序的一部分(动态库不是不能被执行,只是不能被单独执行!!!)。

<code># Makefile

dy-lib=libmymethod.so

static-lib=libmymath.a

# 同时生成多个目标文件

.PHONY:all

all: $(dy-lib) $(static-lib)

$(static-lib):mymath.o

ar -rc $@ $^

mymath.o:mymath.c

gcc -c $^

$(dy-lib):myprint.o mylog.o

gcc -shared -o $@ $^# -shared 生成共享库

myprint.o:myprint.c

gcc -fPIC -c $^# -fPIC 产生位置无关码

mylog.o:mylog.c

gcc -fPIC -c $^

.PHONY:clean

clean:

rm -rf *.o *.a mylib

.PHONY:output

output:

mkdir -p mylib/include

mkdir -p mylib/lib

cp *.h mylib/include

cp *.a mylib/lib

cp *.so mylib/lib

在这里插入图片描述

2.2 动态链接

<code>// main.c

#include "mylog.h"

#include "myprint.h"

int main()

{

Print();

Log("This is a log message!\n");

return 0;

}

与静态链接一样,编译时直接带上 -I 确定头文件路径,-L 确定库文件路径,-l 确定库文件。

gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod

在这里插入图片描述

编译成功了,也即动态链接这个过程并没有问题,但是当程序运行起来之后,却显示 “file not found” 这样的字眼,我们不说已经通过各种选项,告诉编译器头文件、库文件在哪了吗??为什么还会找不到呢??

其实,编译器它确实找到了,但是动态库它不像静态库啊,编译链接完就没它啥事了。当程序运行起来,变为进程,该进程的代码推进时,如果调用了动态库中的数据(即方法),那么操作系统(加载器)就需要去执行动态库的方法,但是加载器不知道动态库在哪啊!

想要搞清楚动态库加载的问题,就需要先清楚那为什么诸如 libc.so.6 这样的系统中的动态库可以被正常加载。系统指令在执行时不需要携带路径,而我们自己的 exe 程序需要路径执行,这是因为环境变量的存在。而编译器有自己默认的搜索头文件、库文件的路径,操作系统同样也有一套自己默认的搜索动态库路径,这些路径都是操作系统内置好的,所以要想让动态库顺利被加载,就需要解决系统的搜索问题!

2.3 动态库的加载方式

关于动态库加载的办法,有以下四种。

2.3.1 拷贝到系统默认的库路径

将动态库拷贝到系统路径的 /lib64 或者 /usr/lib64 即可解决动态库的加载问题(与静态库相似,因此不多言)

2.3.2 建立软链接

<code>sudo ln -s /home/outlier/linux/mylib/test/mylib/lib/libmymethod.so /lib64/libmymethod.so

在这里插入图片描述

动态链接之后,由于加载器找不到动态库的问题,在系统默认库路径下建立一下动态库的软链接,甚至都不需要重新编译链接,该动态库就能够被系统找到了。

2.3.3 环境变量法

LD_LIBRARY_PATH:搜索用户自定义的库路径

将自己的库所在路径添加到系统环境变量的 LD_LIBRARY_PATH 中,同样可以解决动态库加载的问题!

<code>echo $LD_LIBRARY_PATH

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx# 追加环境变量的搜索路径(会话周期性)

在这里插入图片描述

2.3.3 建立动态库路径配置文件

<code>cd /etc/ld.so.conf.d

touch mylib.conf

echo "/home/xxx/xxx/xxx/xxx/mylib/lib" > mylib.conf# 写入路径

ldconfig # 加载配置文件

系统维护动态库的配置文件,文件内存储的是动态库的路径,只要在 /etc/ld.so.conf.d 这个目录下建立一个 .conf 的配置文件,把动态库的路径写入配置文件,那么系统就能够找到动态库的路径。

在这里插入图片描述

动态库路径的配置文件不关注文件名,但需要注意的是,如果存在多个动态库 且 位于同一路径下,可以不需要建立多个路径配置文件,但如果不在同一个路径下,则需要建立多个配置文件来导入多个路径(一个配置文件一个路径)。

实际开发中,企业用的各种第三方库,都是很成熟的库了,因此都采用直接按照到系统库路径下的方式加载动态库,这种方法只是我们平时学习时不使用(因为我们的库不成熟,很简陋,按照到库路径下,有点污染库的意思哈哈哈)。

2.4 动静态库的区别

<code>#include "mymath.h"

#include "mylog.h"

#include "myprint.h"

int main()

{

int ret = add(1, 1);

printf("1 + 1 = %d, errno = %d\n", ret, myerrno);

Print();

Log("This is a log message!\n");

return 0;

}

在这里插入图片描述

在这里插入图片描述

程序编译完后,静态库被删除了,程序不会受到任何影响,因为需要用到的静态库的内容,已经在编译链接时拷贝到自己的可执行程序中了;但是动态库被删除了,程序就运行不起来了,这就是动静态库最本质的区别,动态库并不做拷贝行为,而是在程序运行起来之后,在调用动态库的地方,转而运行动态库的内容,如今动态库没了,程序也就自然运行不起来了。

并且我们可以看到的是,动态库只有一个,但是当多个可执行程序依旧可以正常运行,这也就说明了,动态库是被多个程序所共享的!因此动态库也称为共享库。即便有10个可执行程序,只要调用的是这个动态库的内容,那么在操作系统中,只需要加载这一个动态库,也只加载一次即可,并不需要因为有多个可执行程序而加载多次动态库。

系统指令也可以很好的印证上述论点,诸如 ls、pwd 等指令都用到了系统库 lic.so.6,但系统中就只有这一个 lic.so.6 动态库,并没有存在多份,但各指令依旧正常运行,因为它们都是共享同一个动态库的。

所以,动态库在系统中加载之后,会被所有进程共享! 所有进程共享一个库,系统中重复的代码就减少了,同时也可以大大节省内存。

在这里插入图片描述


如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。