【Linux】动静态库

目录

库的介绍

如何制作一个库

制作静态库

制作动态库

[makefile 的动静态库混合制作](#makefile 的动静态库混合制作)

如何使用库

使用静态库

使用动态库

进程是怎么链接动态库的


库的介绍

在程序的所有源文件都生成目标文件时,通过链接器将目标文件与库链接最终形成一个可执行文件

头文件提供方法的声明,库文件提供方法的实现 +你的代码 = 你的软件

库一般分为静态库和动态库两种。

静态链接的库叫做静态库,动态链接的库叫动态库吗,一个库即可以静态链接,又可以动态链接。同一个库通常可以同时提供静态和动态两种链接方式

复制代码
mylibmath.a    # 静态库版本
mylibmath.so   # 动态库版本

静态库

静态库是一组预先编译好的目标文件(.o 文件)的集合,它们被打包成一个单独的文件,通常以.a.lib为扩展名。静态库在链接时会被完全嵌入到最终的可执行文件中,因此可执行文件在运行时不再需要外部的库文件。

优点:

  • 可执行文件独立,不需要在运行时加载额外的库文件,便于部署。

  • 因为库代码被直接嵌入到可执行文件中,所以运行速度可能会稍快(因为减少了动态加载和重定位的开销)

缺点:

  • 可执行文件体积较大,因为包含了所有用到的库代码。

  • 如果多个程序使用同一个静态库,那么每个程序都会包含一份相同的库代码,造成内存和磁盘空间的浪费。

  • 当静态库更新时,所有使用该库的程序都需要重新链接才能使用新版本。

在 Linux 下其后缀名一般为".a ",windows 下其后缀名一般为 .lib


动态库(共享库)

动态库(共享库)是在运行时被加载到内存的代码库,可以被多个进程共享使用。与静态库不同,动态库的代码不会被复制到可执行文件中,而是在运行时跳转到库执行,执行完后再返回函数调用处

优点:

  • 多个进程共享同一份代码,这样可以节省磁盘空间、内存空间,安装包体积小
  • 替换库文件即可更新所有程序,因此更新方便。

缺点:

  • 一个动态库可能被多个程序使用,一旦库缺失,可能影响多个程序。

在 Linux 下其后缀名一般".so ",windows 下其后缀名一般为**.dll** 。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件


动静态链接的优先规则:

gcc默认生成的二进制程序,是默认优先动态链接的,即:

  • 如果 gcc 没有关于动静态库的选项(默认情况),优先动,其次静;
  • 如果有 -static ,强制静,没有就报错

因为 gcc 天生内置了动态链接的处理方式,而静态链接是 gnu 的 ar 工具处理的。

一个程序也可能既链接了静态库,又链接了动态库

库的命名规则

一般是 :lib + 名称 + .后缀( 比如 .so)+ 版本

查看一个可执行程序所依赖的动态库

ldd 指令:

语法:ldd 可执行程序名

许多指令都是用 c 语言实现的,因为它们依赖 c 语言的库

如何制作一个库

制作静态库

假设实现了以下函数,把这些函数拿给别人用有两种方式:1、直接把头文件(.h文件)和函数具体实现(.c文件)开源 2、把所有函数具体实现(.c文件)编译成 .o 文件,把所有 .o 文件打包成以 .a 为后缀的库文件,即静态库。注意:静态库的命名必须是 libXXX.a 的形式。

cpp 复制代码
/////////////add.h/////////////////
 #ifndef __ADD_H__
 #define __ADD_H__ 
 int add(int a, int b); 
 #endif // __ADD_H__


 /////////////add.c/////////////////
 #include "add.h"
 int add(int a, int b)
 {
     return a + b;
 }


 /////////////sub.h/////////////////
 #ifndef __SUB_H__
 #define __SUB_H__ 
 int sub(int a, int b); 
 #endif // __SUB_H__


 /////////////sub.c/////////////////
 #include "sub.h"
 int sub(int a, int b)
 {
     return a - b;
 }

在命令行制作静态库:

bash 复制代码
[root@localhost linux]# ls
add.c add.h test.c sub.c sub.h

[root@localhost linux]# gcc -c add.c -o add.o
[root@localhost linux]# gcc -c sub.c -o sub.o

生成静态库
[root@localhost linux]# ar -rc libmymath.a add.o sub.o 
静态库制作完成
ar是gnu归档工具,rc表示(replace and create)

查看静态库中的目录列表
[root@localhost linux]# ar -tv libmymath.a 
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 add.o
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 sub.o

t:列出静态库中的文件
v:verbose 详细信息

在 makefile 中制作静态库,并打包发布:

bash 复制代码
lib=libmymath.a

$(lib):add.o sub.o
    ar -rc $@ $^

add.o:add.c
    gcc -c $^ -o $@

sub.o:sub.c
    gcc -c $^ -o $@

.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   

现在只需要把 lib 文件夹发布,别人就可以使用你的库函数了

制作动态库

cpp 复制代码
///////////////////myprintf.h////////////////////
#pragma once

#include <stdio.h>

void Print();        

///////////////////myprintf.c////////////////////
#include "myprintf.h"

void Print()                           
{
    printf("hello new world\n");
    printf("hello new world\n");
    printf("hello new world\n");
    printf("hello new world\n");
}

///////////////////mylog.h////////////////////
#pragma once

#include <stdio.h>

void Mylog(const char*);            

///////////////////mylog.c////////////////////
#include "mylog.h"

void Mylog(const char* message)
{
    printf("warning:%s\n",message);         
}

在命令行制作动态库:

bash 复制代码
[root@localhost linux]# gcc -fPIC -c myprintf.c -o myprintf.o
[root@localhost linux]# gcc -fPIC -c mylog.c -o mylog.o

[root@localhost linux]# gcc -shared -o libmymath.so *.o
动态库制作完成

makefile 的动静态库混合制作

现在,要在 makefile 里既使用 add.o 、sub.o 制作静态库,又使用 myprintf.o、mylog.o 制作动态库:

bash 复制代码
static-lib=libmymath.a
dy-lib=libmymethod.so

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

$(static-lib):add.o sub.o
    ar -rc $@ $^

add.o:add.c
    gcc -c $^ -o $@

sub.o:sub.c
    gcc -c $^ -o $@

$(dy-lib):myprintf.o mylog.o
    gcc -shared $^ -o $@

myprintf.o:myprintf.c
    gcc -fPIC -c $^ -o $@

mylog.o:mylog.c
    gcc -fPIC -c $^ -o $@                        

.PHONY:clean

clean:
    rm -rf *.o *.a *.so mylib

.PHONY:output

output:
     mkdir -p mylib/include
     mkdir -p mylib/lib
     cp *.h mylib/include
     cp *.a mylib/lib  
     cp *.so mylib/lib

如何使用库

使用静态库

假设现在我下载了 lib 文件夹,并且 lib 文件夹在 main.c 的同一级目录里

cpp 复制代码
 ///////////test.c////////////////
 #include <stdio.h>
 #include "add.h"
 #include "sub.h"
 
 int main()
 {
    printf("add(1,2) = %d\n",add(1,2));
    printf("sub(1,2) = %d\n",sub(1,2));
    return 0;
 }

编译报错:找不到文件 mymath.h。

gcc 编译器在寻找头文件(.h)时,要在1、默认的路径(/usr/include)下寻找:

2、当前与源文件同一级目录下寻找(头文件(.h) 不能在同一级目录的某个文件夹)。

解决方法:

1、#include "lib/include/mymath.h"

2、指定 gcc 在特定目录下寻找(如果 gcc 在默认路径和与源文件同一级目录都没有找到,就在特定目录下寻找:

gcc main.c -I(大写 i)./lib/include/

发现仍然有报错,但是 gcc -c main.c -I./lib/include/ (只生成 main.o 文件)没有报错,说明可能是最后链接时报错。实际上,gcc 在链接库时,也会到默认的路径寻找/lib64),如果想让 gcc 在特定路径寻找库,也要带选项:

gcc main.c -I (大写 i)./lib/include/ -L ./lib/mymathlib/ -l (link 的首字母)mymath (必须使用 -l 精确到库名,-l 与库名之间最好不要有空格,因为 mymathlib 这个目录下可能包含多个库,这时的库名要去掉 ilb 前缀和 .a 后缀 ,如果要使用多个库,在后面多跟几个 -l库名 就可以了)

我们发现每次编译都要写一大堆指令,为了编译方便,直接把头文件和库文件拷贝到默认搜索路径

bash 复制代码
[hxh@VM-16-12-centos 2026_2_1]$ sudo cp lib/include/add.h lib/include/sub.h /usr/include
[sudo] password for hxh: 
[hxh@VM-16-12-centos 2026_2_1]$ ls /usr/include/add.h
/usr/include/add.h
[hxh@VM-16-12-centos 2026_2_1]$ sudo cp lib/mymathlib/libmymath.a /lib64/
[hxh@VM-16-12-centos 2026_2_1]$ ls /lib64/libmymath.a
/lib64/libmymath.a
[hxh@VM-16-12-centos 2026_2_1]$ rm -rf a.out
[hxh@VM-16-12-centos 2026_2_1]$ gcc test.c 
/tmp/ccUtKmON.o: In function `main':
test.c:(.text+0xf): undefined reference to 'add'
test.c:(.text+0x2f): undefined reference to 'sub'
collect2: error: ld returned 1 exit status
[hxh@VM-16-12-centos 2026_2_1]$ gcc test.c -lmymath
[hxh@VM-16-12-centos 2026_2_1]$ ll
total 20
-rwxrwxr-x 1 hxh hxh 8472 Feb  2 12:05 a.out
drwxrwxr-x 4 hxh hxh 4096 Feb  1 13:49 lib
-rw-rw-r-- 1 hxh hxh  162 Feb  1 13:54 test.c
[hxh@VM-16-12-centos 2026_2_1]$ ./a.out
add(1,2) = 3
sub(1,2) = -1

直接把头文件和库文件拷贝到默认搜索路径之后,直接 gcc test.c 发现不认识 add 和 sub,这是因为 gcc 在默认搜索路径搜索库时,只认识语言内置的库,使用第三方库,无论如何都要有 -l (link) 选项 。把头文件和库文件拷贝到默认搜索路径,这个动作叫做库的安装

除了把头文件和库文件拷贝到默认搜索路径,还可以在默认搜索路径建立头文件和库文件的软链接

bash 复制代码
[hxh@VM-16-12-centos 2026_2_1]$ sudo ln -s /home/hxh/2026_2_1/lib/include /usr/include/myinc
[sudo] password for hxh: 
[hxh@VM-16-12-centos 2026_2_1]$ ll /usr/include/myinc
lrwxrwxrwx 1 root root 30 Feb  2 12:29 /usr/include/myinc -> /home/hxh/2026_2_1/lib/include
[hxh@VM-16-12-centos 2026_2_1]$ sudo ln -s /home/hxh/2026_2_1/lib/mymathlib/libmymath.a /lib64/libmymath.a
[hxh@VM-16-12-centos 2026_2_1]$ ll /lib64/libmymath.a
lrwxrwxrwx 1 root root 44 Feb  2 12:31 /lib64/libmymath.a -> /home/hxh/2026_2_1/lib/mymathlib/libmymath.a

现在要稍微修改一下 test.c 中头文件的包含方式:

cpp 复制代码
#include "myinc/add.h"
#include "myinc/sub.h"                                    
#include <stdio.h>
int main()
{
    printf("add(1,2) = %d\n",add(1,2));
    printf("sub(1,2) = %d\n",sub(1,2));
    return 0;
}

使用动态库

cpp 复制代码
//#include "add.h"
//#include "sub.h"
#include <stdio.h>
#include "myprintf.h"
#include "mylog.h"

int main()
{
    //printf("add(1,2)=%d\n",add(1,2));
    //printf("sub(1,2)=%d\n",sub(1,2));

    Print();
    Mylog("hello");                           
                         
    return 0;            
}                        

以上是测试代码,先用使用静态的方法照搬使用动态库:

bash 复制代码
[hxh@VM-16-12-centos 2026_2_2]$ gcc test.c -I mylib/include -L mylib/lib -lmymethod
[hxh@VM-16-12-centos 2026_2_2]$ ll
total 20
-rwxrwxr-x 1 hxh hxh 8384 Feb  2 14:37 a.out
drwxrwxr-x 4 hxh hxh 4096 Feb  2 14:23 mylib
-rw-rw-r-- 1 hxh hxh  243 Feb  2 14:36 test.c
[hxh@VM-16-12-centos 2026_2_2]$ ./a.out
./a.out: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory

程序报错说当加载动态库时打不开动态库,因为没找到这样的动态库,可是我们明明在编译时告诉 gcc 我们要使用的动态库就是 my/lib 这一路径的 libmymethod.so 。原因是我们只是告诉了编译器动态库的位置,但是操作系统不知道在哪里。为什么静态库没有出现这样的问题呢,因为静态库已经内嵌到代码里了,可执行程序运行时不需要再寻找库。

那么怎么样才能让运行中的可执行程序可以找到我们的库呢,有以下几种方法:

1、直接把库拷贝到 /lib64 ,这是最常用的方式

2、建立从 /lib64 到库的软链接

3、通过环境变量 LD_LIBRARY_PATH (LD:load),该环境变量专门用来存放用户自定义库的搜索路径,不覆盖式的添加路径:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:[新路径]

cpp 复制代码
[hxh@VM-16-12-centos 2026_2_2]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hxh/2026_2_2/mylib/lib
[hxh@VM-16-12-centos 2026_2_2]$ echo $LD_LIBRARY_PATH
:/home/hxh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/hxh/2026_2_2/mylib/lib
[hxh@VM-16-12-centos 2026_2_2]$ ldd a.out
	linux-vdso.so.1 =>  (0x00007ffd6f9c8000)
	libmymethod.so => /home/hxh/2026_2_2/mylib/lib/libmymethod.so (0x00007fba76a20000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fba76652000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fba76c22000)

但是这样我们重启bash ,LD_LIBRARY_PATH 又会变为原来的样子,我们要修改家目录的 .bashrc 隐藏文件,就能永久修改 LD_LIBRARY_PATH:

bash 复制代码
# .bashrc                                                                               

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=

# User specific aliases and functions
alias vim='/home/hxh/.VimForCpp/nvim'
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:[新路径]

或者修改家目录的 .bash_profile 隐藏文件

bash 复制代码
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH
# 添加 export LD_LIBRARY_PATH=/home/hxh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/hxh/2026_2_2/mylib/lib  

这种方法仅对当前用户有效。如果要对所有用户有效,应使用第1、2、4种方法。

4、配置 /etc/ld.so.conf.d/

在 /etc/ld.so.conf.d/ 路径下创建一个后缀为 .conf 的配置文件,并用 vim 打开(普通用户 sudo vim):

bash 复制代码
# 添加库所在的路径 /home/hxh/2026_2_2/mylib/lib
# 一行一个链路径                                            
~                                            
~                                            
~                                            
~                                            
~                                            
~                                            
~                                            

保存退出输入 idconfig (普通用户 sudo ldconfig),完成

进程是怎么链接动态库的

一个进程可能使用多个动态库,所以内存中一定存在多个动态库,操作系统采用"先描述,再组织"的方式管理所有动态库,不管操作系统是怎么管理的,它一定清楚库的加载情况,即我要加载哪些库,哪些库还没有被加载,哪些库已经被加载了。当一个进程要使用动态库,它一定会先询问操作系统是否加载了我需要的库,如果没有,操作系统立刻加载,并且帮助进程建立物理内存的库的地址与虚拟地址的映射关系,如果已经加载,直接建立映射关系,不再重复加载库。

相关推荐
Y1rong2 小时前
linux之线程池
linux
H Journey2 小时前
Docker swarm 集群搭建实战
运维·docker·容器
划水的code搬运工小李2 小时前
Ubuntu下挂载NTFS格式磁盘
linux·运维·ubuntu
炸裂狸花猫2 小时前
开源域名代理与流量限制方案 - Cloudflare + Ingress + 自签名证书
运维·云原生·容器·kubernetes·cloudflare·waf·免费域名证书
佑白雪乐2 小时前
<Linux基础12集>1-11集大复习Review
linux·运维·策略模式
试试勇气2 小时前
Linux学习笔记(十四)--进程间通信
linux·笔记·学习
鹏大师运维2 小时前
信创桌面操作系统上的WPS外观界面配置
linux·运维·wps·麒麟·统信uos·中科方德·整合模式
CS_Zero2 小时前
Ubuntu系统安装CH340&CH341串口驱动
linux·ubuntu
落羽的落羽2 小时前
【Linux系统】从零实现一个简易的shell!
android·java·linux·服务器·c++·人工智能·机器学习