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

