Linux系统编程系列之动静态库


一、对库的理解

理解库

什么是库?比如C语言中的math库,python中的numpy库,库其实是预先编译好的、二进制代码模块集合,其实就是把通用功能的代码提前写好、编译成特定格式的文件,使用者不用重复编写这些通用逻辑,直接调用即可。核心还是方便人们使用!

制作库的理解:一个程序经过预处理、编译、汇编形成.o(Linux下) / .obj文件(Windows下),然后把这些.o文件链接形成可执行,那库和这个有什么关系呢? 我想用你写的代码,可以把.o文件(也需要头文件)拿过来链接就可以使用了,但是一个正常的项目库,比如numpy库这种,.o文件非常的多,所以对.o文件进行打包处理形成了库,动静态库的处理方法也一定不相同! 所以,我们用的库大多数都是.o的集合,提供功能和方法方便开发者使用!

理解动静态库

有了库的理解之后我们再来理解一下动静态库,在动静态链接中我们已经提到一次了,这里简单复习和扩展一下。

静态库:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库

动态库:程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

随便写一份代码来看看动静态库的链接。

ldd---查看一个可执行所依赖的库文件。

依赖的就是libc.so.6, 这是个软链接。C标准库,在/lib64/下存在着大量的动态库,这也是系统默认的查找路径,后面还会提。

Linux中.so为后缀的文件是动态库,.a文件为后缀的是静态库。

Windows中,.dll为后缀的是动态库,.lib为后缀的是静态库。

file + 文件名可以查看文件信息

显示出来的符合预期,dynamically linked---动态链接。

g++ / gcc默认是动态链接的,带-static选项可以让它强制静态链接。

statically linked----静态链接。同时ldd也显示不是动态可执行。

动静态库优缺点

静态库:程序在编译链接的时候把库的代码链接到可执行文件中。

优点:使用静态库生成可执行程序可以直接运行,不再需要动态库,对于自己写的库不需要手动链接。

缺点:使用静态库生成的可执行程序空间会大很多,由上图可以看出差距之大。而且这只是包含了一个头文件,当有多个静态程序同时加载相同的库,内存还会存在大量的重复代码。所以系统默认不用静态库。

动态库:动态库是在程序运行阶段才引入代码的,程序地址空间中我们提到过堆栈相对而生,中间是共享区,动态库就被加载到了这,运行可执行的时候,可执行先被加载到内存中,当要使用动态库的函数的时候,先加载到物理内存中,通过页表映射到虚拟内存的共享区中,这样就可以使用了。

优点:能节省磁盘和内存空间,并且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存中不会存在重复代码

缺点: 运行时依赖动态库,并且自制动态库的使用相对复杂,因为需要手动链接。

二、制作静态库

比如模拟一个加减乘除

mymath.c

cpp 复制代码
#include"mymath.h"

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 myerrno = 0;
int divi(int x,int y)
{
  if(y == 0)
  {
    myerrno = 1;
    return -1;
  }
   return x / y;
}

mymath.h

cpp 复制代码
#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 divi(int x,int y);

main.c

cpp 复制代码
#include<stdio.h>
#include"mymath.h"


int main()
{
  int x = divi(4,2);
  printf("x:%d\n",x);

  return 0;
}

过程

怎么生成静态库呢?首先肯定要生成.o文件,然后使用ar -rc打包

ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面是一些常用选项:一般就使用ar -rc

-r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的

-c(create):建立静态库文件

t:列出静态库中的文件

v:详细信息

用makefile生成就行,写法:

makefile 复制代码
libmymath.a:mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^ -o $@
clean:
	rm -rf main

写完直接gcc main.c发现失败了,报错原因是divi未定义,这个其实可以理解,系统又不知道要主动链接你,所以需要g++需要 -l 选项,表示指定我链接的是这个库,另外,库名字是去掉前缀lib和后缀的,也就是说应该是gcc main.c -o main -lmymath, 在线程那里我们还会用到。链接多个库的话就带多个-l即可。

一输入发现又失败了

这里显示cannot find -lmymath也就是说,系统默认路径下找你这个库文件没有找到,当然找不到了!你只让我找库名为这个的,你又没告诉我在哪找!-L +路径,表示在指定库路径中寻找!

细节

这里有个非常恶心人的坑,就是如果你用的是cpp文件,这里可能会编译不通过,但是一切都没问题了啊,此时报错又变成了未定义,我查了查,发现:
用g++编译文件 main.cpp,但库文件 libmymath.a 是由 C 文件 mymath.c 编译生成的 ------C 和 C++ 的函数名修饰规则不同:
C 语言的 divi(int, int) 编译后,符号名是简单的divi;
C++ 会对函数名进行 "修饰",比如 divi(int, int) 会被编译为 _Z4divii 这类复杂符号。
所以,这里统一用.c即可。。。

编译成功后,删除.a文件,仍然可以运行

这里再提一个点,用系统的库的时候,是不需要加任何选项的,因为gcc的头文件默认搜索路径是:/usr/include 库文件的默认搜索路径是:/lib64 或者 /usr/lib64
那头文件是如何搜索的呢? 我们知道包含系统库的使用使用的是< >,自己的是" ",如果是< > 就会去/usr/include/下去找,找不到就报错,如果是" ",先去当前目录找,找不到再去/usr/include/下去找,找不到就会报错,当然,gcc也有选项-I, 表示指定搜索头文件路径。

那我们把自己的libmymath.a放到/lib64下就可以只gcc main.c 了吗?不可以!gcc不会主动链接你!只会主动链接核心系统库,比如libc.so,如果随便链接,会导致可执行文件非常大并且冲突。你需要告诉gcc链接哪个库,当然这样就不需要指定路径了,/usr/include/下找得到。并且格式必须是libxxx.a,gcc只会搜索正确格式的库!

当然,这里一整一大串不是很舒服,我们就可以采用一些方式:

1.可以将文件拷贝到系统路径下

sudo cp lib/include/mymath.h /usr/include/

sudo cp lib/XXXX/libmath.a /lib64/ or /usr/lib64/

不建议这么做。

2.建立软链接----软链接的作用体现出来了!可以跨目录!

使用绝对路径

sudo ln -s xxxx /usr/include/xxx

sudo ln -s xxx /lib64/libxxx.a


三、制作动态库

过程

生成.o的时候要带 -fPIC选项,表示产生位置无关码(position independent code),然后gcc带上shared选项生成动态库!

cpp 复制代码
libmymath.so:mymath.o
	gcc -shared $^ -o $@
mymath.o:mymath.c
	gcc -fPIC $@ -c $^

-fPIC作用于编译阶段,其目的是告诉编译器产生与位置无关的代码,这时产生的代码中没有绝对地址,全都是相对地址.

生成完用法也和上面一样使用即可。

gcc main.c -o main -lmymath -L ./

另外:如果既存在动态库又存在静态库,不带static选项,系统会默认链接动态库,如果动态库不存在,系统只能默认链接静态库。

编译成功后,删除.so文件,发现无法运行,因为是动态库,需要动态去找!

找动态库的方式

我们知道,.so是运行时才会被加载到内存的,这就说明他一定能被找到!但是系统不会去找啊,那为什么上面提到的./main可以编译成功呢??

注意:gcc main.c -o main -lmymath -L ./这里是带路径了的!所以我们运行main的时候可以找得到!

有没有其他方式呢??包有的,首先就是上面提到的两种,一种拷贝到/usr/lib64/下,这种不推荐,另一种建立软链接,比如:sudo ln -s /home/caugyj/libmymath.so /usr/lib64/libmymath.so,一定要使用绝对路径

还有两种方式:

1.环境变量 LD_LIBRARY_PATH 修改这个环境变量加上自己的库的所在路径, xxx/mylib/lib

2 . /etc/ld.so.conf.d建立自己的动态库路径的配置文件,

(注意以.conf结尾,文件名无所谓,在root账号下使用或者sudo)

也是加上自己的库的所在路径,然后重新输入ldconfig指令即可.

这个永久有效,关掉xshell再打开可以使用

实际情况,我们用的库都是别人成熟的库,都采用直接安装到系统的方式!

动态库在进程运行的时候,是要被加载的(静态库没有),常见的动态库被所有的可执行程序(动态链接的),都要使用,动态库--共享库

所以,动态库在系统中加载之后,会被所有进程共享! -shared

相关推荐
济61717 小时前
linux(第十三期)--filezilla使用方法(实现ubuntu和windows11文件互传)-- Ubuntu20.04
linux·运维·ubuntu
HIT_Weston17 小时前
91、【Ubuntu】【Hugo】搭建私人博客:侧边导航栏(五)
linux·运维·ubuntu
oMcLin18 小时前
如何在 Rocky Linux 8.6 上配置并调优 Nginx 与 Lua 脚本,提升 API 网关的性能与并发处理能力
linux·nginx·lua
Yana.nice18 小时前
Linux目录结构说明
linux
EndingCoder18 小时前
箭头函数和 this 绑定
linux·前端·javascript·typescript
食咗未18 小时前
Linux iptables工具的使用
linux·运维·服务器·驱动开发·网络协议·信息与通信
tech-share18 小时前
【无标题】IOMMU功能测试软件设计及实现 (二)
linux·架构·系统架构·gpu算力
时兮兮时18 小时前
Linux 服务器后台任务生存指南
linux·服务器·笔记
dz小伟18 小时前
从用户空间open()到驱动open()的完整调用链深度解析
linux