【Linux】动态库与静态库


在这里插入图片描述

1. 认识静态库与动态库

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • 一个动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制,允许物理内存中的一份动态库被 需要用到该库的所有进程 共用,节省了内存和磁盘空间。

本文测试准备代码:

// ---------- add.h ----------
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif

// ---------- 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.c ----------
#include "sub.h"

int sub(int a, int b)
{
    return a - b;
}


// ---------- main.c ----------
#include <stdio.h>
#include "add.h"
#include "sub.h"

int main()
{
    int a = 10;
    int b = 20;
    printf("add(%d, %d) = %d\n", a, b, add(a, b));

    a = 100;
    b = 20;
    printf("sub(%d, %d) = %d\n", a, b, sub(a, b));

    return 0;
}

2. 手动创建并测试静态库

2.1 生成静态库

我们先使用命令生成一个静态库,并测试是否可以使用:

# 把上面的代码拿过来
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l
total 20
-rw-rw-r-- 1 ubuntu ubuntu  61 Apr 20 20:13 add.c
-rw-rw-r-- 1 ubuntu ubuntu  65 Apr 20 20:13 add.h
-rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c
-rw-rw-r-- 1 ubuntu ubuntu  61 Apr 20 20:15 sub.c
-rw-rw-r-- 1 ubuntu ubuntu  65 Apr 20 20:14 sub.h

# 生成 .o 文件,准备打包为静态库
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -c add.c -o add.o
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -c sub.c -o sub.o

# 生成静态库
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ar -rc libmymath.a add.o sub.o
# ar 是 gnu 归档工具,常用于将目标文件打包为静态库
# rc 表示(replace and create)

# 查看静态库中的目录列表
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ar -tv libmymath.a 
rw-r--r-- 0/0   1376 Jan  1 08:00 1970 add.o
rw-r--r-- 0/0   1376 Jan  1 08:00 1970 sub.o
# t:列出静态库中的文件
# v:verbose 详细信息

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc main.c -L. -lmymath
# -L:指定库路径
# -l:指定库名

# 测试静态库
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.h  add.o  a.out  libmymath.a  main.c  sub.c  sub.h  sub.o
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ./a.out 
add(10, 20) = 30
sub(100, 20) = 80

# 测试目标文件生成后,静态库删掉,程序照样可以运行
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ rm libmymath.a 
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ./a.out 
add(10, 20) = 30
sub(100, 20) = 80

2.2 打包静态库

如果想把这个静态库给别人使用,我们需要将 库(.a)和 头文件(.h)一起打包;这样别人在看到头文件的时候,就知道我们的库中封装了哪些方法。

使用 Makefile 进行自动化编译:

# ---------- Makefile ----------
libmymath.a:sub.o add.o
	ar -rc $@ $^

%.o:%.c
	gcc -c $<

.PHONY:clean
clean:
	rm -rf *.o output libmymath.a

# 将相关文件打包到 output 文件夹
.PHONY:output
output:
	mkdir output
	cp -rf *.h output
	cp libmymath.a output
# ubuntu 20.04 实机演示

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l
total 24
-rw-rw-r-- 1 ubuntu ubuntu  61 Apr 20 20:13 add.c
-rw-rw-r-- 1 ubuntu ubuntu  65 Apr 20 20:13 add.h
-rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c
-rw-rw-r-- 1 ubuntu ubuntu 188 Apr 21 13:38 Makefile
-rw-rw-r-- 1 ubuntu ubuntu  61 Apr 20 20:15 sub.c
-rw-rw-r-- 1 ubuntu ubuntu  65 Apr 20 20:14 sub.h

# 构建静态库
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make
gcc -c sub.c
gcc -c add.c
ar -rc libmymath.a sub.o add.o
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l
total 36
-rw-rw-r-- 1 ubuntu ubuntu   61 Apr 20 20:13 add.c
-rw-rw-r-- 1 ubuntu ubuntu   65 Apr 20 20:13 add.h
-rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 add.o
-rw-rw-r-- 1 ubuntu ubuntu 2960 Apr 21 13:43 libmymath.a
-rw-rw-r-- 1 ubuntu ubuntu  243 Apr 20 20:33 main.c
-rw-rw-r-- 1 ubuntu ubuntu  188 Apr 21 13:38 Makefile
-rw-rw-r-- 1 ubuntu ubuntu   61 Apr 20 20:15 sub.c
-rw-rw-r-- 1 ubuntu ubuntu   65 Apr 20 20:14 sub.h
-rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 sub.o

# 打包
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make output 
mkdir output
cp -rf *.h output
cp libmymath.a output
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l
total 40
-rw-rw-r-- 1 ubuntu ubuntu   61 Apr 20 20:13 add.c
-rw-rw-r-- 1 ubuntu ubuntu   65 Apr 20 20:13 add.h
-rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 add.o
-rw-rw-r-- 1 ubuntu ubuntu 2960 Apr 21 13:43 libmymath.a
-rw-rw-r-- 1 ubuntu ubuntu  243 Apr 20 20:33 main.c
-rw-rw-r-- 1 ubuntu ubuntu  188 Apr 21 13:38 Makefile
drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:43 output
-rw-rw-r-- 1 ubuntu ubuntu   61 Apr 20 20:15 sub.c
-rw-rw-r-- 1 ubuntu ubuntu   65 Apr 20 20:14 sub.h
-rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 sub.o

# 查看 output
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ tree output/
output/
├── add.h
├── libmymath.a
└── sub.h

0 directories, 3 files

2.3 使用静态库

我们已经将静态库以及相关头文件打包到了 output 文件夹,现在我们新建一个 TestStaticLib 文件夹进行测试:

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ mkdir TestStaticLib
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l
total 44
-rw-rw-r-- 1 ubuntu ubuntu   61 Apr 20 20:13 add.c
-rw-rw-r-- 1 ubuntu ubuntu   65 Apr 20 20:13 add.h
-rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 add.o
-rw-rw-r-- 1 ubuntu ubuntu 2960 Apr 21 13:43 libmymath.a
-rw-rw-r-- 1 ubuntu ubuntu  243 Apr 20 20:33 main.c
-rw-rw-r-- 1 ubuntu ubuntu  188 Apr 21 13:38 Makefile
drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:43 output
-rw-rw-r-- 1 ubuntu ubuntu   61 Apr 20 20:15 sub.c
-rw-rw-r-- 1 ubuntu ubuntu   65 Apr 20 20:14 sub.h
-rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 sub.o
drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:56 TestStaticLib

# 将 output 移动到 TestStaticLib 文件夹,并改名为 StaticLib
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ mv output TestStaticLib/
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ cd TestStaticLib/
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ mv output StaticLib
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ls
StaticLib

TestStaticLib 目录中新建测试文件:

// ---------- test.c ----------
#include <stdio.h>
#include "add.h"
#include "sub.h"

int main()
{
    int a = 10;
    int b = 20;
    printf("add(%d, %d) = %d\n", a, b, add(a, b));

    a = 100;
    b = 20;
    printf("sub(%d, %d) = %d\n", a, b, sub(a, b));

    return 0;
}

使用 Makefile 进行自动化编译:

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ls
StaticLib  test.c
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ touch Makefile

# 编写 Makefile
ubuntu:~/Linux/test_4_20/TestStaticLib$ vim Makefile
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ cat Makefile 
test:test.c
	gcc -o $@ $^ -I ./StaticLib -L ./StaticLib -l mymath
	# -I:指定头文件所在目录
	# -L:指定静态库所在目录
	# -l:指定库名

.PHONY:clean
clean:
	rm -f test

测试:

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ make
gcc -o test test.c -I ./StaticLib -L ./StaticLib -l mymath
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ls -l
total 32
-rw-rw-r-- 1 ubuntu ubuntu    99 Apr 21 14:21 Makefile
drwxrwxr-x 2 ubuntu ubuntu  4096 Apr 21 13:43 StaticLib
-rwxrwxr-x 1 ubuntu ubuntu 16816 Apr 21 14:21 test
-rw-rw-r-- 1 ubuntu ubuntu   243 Apr 21 14:13 test.c
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ./test 
add(10, 20) = 30
sub(100, 20) = 80

3. 库搜索路径

  • 从左到右搜索 -L 指定的目录
  • 由环境变量指定的目录(LIBRARY_PATH)
  • 由系统指定的目录
    • / user / lib
    • / user / local / lib

下面测试使用动态库时,需要我们在库搜索路径中手动添加动态库!

4. 手动创建并测试动态库

4.1 生成动态库

使用命令手动生成静态库:

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.h  main.c  sub.c  sub.h
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -fPIC -c sub.c add.c
# fPIC:产生位置无关码(position independent code)
# 编译产生的代码没有绝对位置,只有相对位置,从而可以在任意地方调用生成的动态库

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.h  add.o  main.c  sub.c  sub.h  sub.o
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -shared -o libmymath.so *.o
# shared:表示生成共享库格式
# 库名规则:lib*.so

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.h  add.o  libmymath.so  main.c  sub.c  sub.h  sub.o

4.2 打包动态库

将 动态库(.so)和 头文件(.h)打包到一个文件夹:

# ---------- Makefile ----------
libmymath.so:add.o sub.o
	gcc -shared -o $@ $^

%.o:%.c
	gcc -fPIC -c $<

.PHONY:clean
clean:
	rm -rf libmymath.so *.o DynamicLib

.PHONY:DynamicLib
DynamicLib:
	mkdir DynamicLib
	cp *.h DynamicLib
	cp libmymath.so DynamicLib
# ubuntu 20.04 实机演示

# 构建动态库
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.h  main.c  Makefile  sub.c  sub.h  TestStaticLib
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make
gcc -fPIC -c add.c
gcc -fPIC -c sub.c
gcc -shared -o libmymath.so add.o sub.o
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.o         main.c    sub.c  sub.o
add.h  libmymath.so  Makefile  sub.h  TestStaticLib

# 打包
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make DynamicLib 
mkdir DynamicLib
cp *.h DynamicLib
cp libmymath.so DynamicLib
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls
add.c  add.o       libmymath.so  Makefile  sub.h  TestStaticLib
add.h  DynamicLib  main.c        sub.c     sub.o

# 查看打包文件
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ tree DynamicLib/
DynamicLib/
├── add.h
├── libmymath.so
└── sub.h

0 directories, 3 files

4.3 使用动态库

故技重施,创建 TestDynamicLib 文件夹进行测试:

# 把上面的动态库拿过来,并创建 Makefile 和 test.c
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ tree TestDynamicLib/
TestDynamicLib/
├── DynamicLib
│   ├── add.h
│   ├── libmymath.so
│   └── sub.h
├── Makefile
└── test.c
# test.c 与静态库保持一致

# Makefile
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ cat TestDynamicLib/Makefile 
test:test.c
	gcc -o $@ $^ -I ./DynamicLib -L ./DynamicLib -l mymath

.PHONY:clean
clean:
	rm -f test

直接编译试试?

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ cd TestDynamicLib/

# 编译
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ make
gcc -o test test.c -I ./DynamicLib -L ./DynamicLib -l mymath
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ls
DynamicLib  Makefile  test  test.c

# 运行
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ./test 
./test: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory

发现报错了,系统找不到我们的动态库,为什么?

上面我们提到过库搜索路径,我们只是在编译时指定了路径,只有编译器知道动态库的位置,编译完成后就没有人知道去哪里找动态库了!所以可执行程序是找不到动态库的!

那为什么静态库没事呢?因为静态库是直接把库链接到可执行文件中,编译完成后把静态库删掉都不会影响可执行文件

动态库不一样,程序每次执行都要去磁盘上搜索动态库的位置,然后链接,由于我们并没有指定动态库的位置,所以找不到!

如何解决?

一个比较简单且没有副作用的方法是修改 LD_LIBRARY_PATH 环境变量,因为我们只是想测试,并不想真的把这个动态库添加到系统中,而环境变量在我们关掉 xshell 后就会重置,所以我们选择这种方法进行测试:

ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ pwd
/home/ubuntu/Linux/test_4_20/TestDynamicLib

# 修改环境变量
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ export LD_LIBRARY_PATH=/home/ubuntu/Linux/test_4_20/TestDynamicLib/DynamicLib

# 查看环境变量
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ echo $LD_LIBRARY_PATH
/home/ubuntu/Linux/test_4_20/TestDynamicLib/DynamicLib
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ls
DynamicLib  Makefile  test  test.c

# 执行测试程序
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ./test 
add(10, 20) = 30
sub(100, 20) = 80

# 测试成功

5. 动静态库优先级

  • 如果我们同时提供动态库和静态库,gcc 默认使用的是动态库;
  • 如果我们非要静态链接,我们必须使用 static 选项;
  • 如果我们只提供静态库,那我们的可执行程序也没办法,只能对该库进行静态链接,但是程序不一定整体是静态链接的。
  • 如果我们只提供动态库,默认只能动态链接,如果非要静态链接,会发生链接报错!

END

相关推荐

  1. linux动态静态

    2024-04-23 11:38:03       31 阅读
  2. 静态动态

    2024-04-23 11:38:03       9 阅读
  3. Linux静态动态使用总结

    2024-04-23 11:38:03       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-23 11:38:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-23 11:38:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-23 11:38:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-23 11:38:03       18 阅读

热门阅读

  1. 【领导力】削足适履与领导力

    2024-04-23 11:38:03       15 阅读
  2. 中国的微观调查数据总结

    2024-04-23 11:38:03       17 阅读
  3. vtk.vtkProcrustesAlignmentFilter()使用方法

    2024-04-23 11:38:03       12 阅读
  4. 认识线程池

    2024-04-23 11:38:03       11 阅读
  5. StarRocks用户权限管理

    2024-04-23 11:38:03       11 阅读