动静态链接动静态库制作与使用

news2025/8/7 6:00:28

前置知识

  • 程序的编译与链接:动静态库属于程序链接阶段的概念,如果对程序的编译链接过程不太熟悉,可以先看一下着篇文章
  • gcc&动静态链接:这篇文章讲解了如何在Linux环境下用gcc完成编译链接的每一步操作

链接库

在链接的过程中,除了把多个.o文件进行链接,其实还要链接库一些库,比如printf的实现……这些方法也要链接进来

一般使用一个库要有两个操作:包头文件(在代码中#include)、链接函数库(在命令行中)

  • 头文件

    内含函数的声明给我们提供了可以使用的方法(函数的声明)

    (我们平时在开发环境看到的语法提示,其实就是通过头文件帮我们搜索的)

  • 库文件(函数的实现)

    提供了可以使用的方法的实现,以供链接,形成我们自己的可执行程序

使用案例:printf()

​ 当我们要使用printf这个库函数,首先要包一个头文件:

#include <stdio.h>

stdio.h这个文件中会有printf的声明:

int printf(const char *restrict __format, …);

那函数的定义在哪?
这些C语言提供的库一般放在/lib64/libc*目录下:

这些库即使我们不手动去链接,编译器也会自动到这个目录下找到所需库文件链接进来

如果是我们自己实现的库,就需要手动链接进来(方法在后面)

可以链接的库分为两类:动态库、静态库,这两种库的链接方式也对应动静两种

动态库:
Linux(.so)
Windows(.dll)
动态链接
静态库:
Linux(.a)
Windows(.lib)
静态链接

动静态链接的感性认识

这里为了让大家感性认识一下动、静态链接的区别,给大家引个小故事,方便理解

《动态链接》

我有个朋友是一个网瘾少年,学校里不允许带电子设备,所以作为一个刚入学的高一学生,一进学校就提前到学长那里打听好了,出校门往东500米就有一家网吧”极速码头“。他到了宿舍,睡了三十分钟觉,看了30分钟书,扣了20分钟手,然后就想去打游戏,计划说打完游戏就回来接着看书、上课。于是按照学长说的那个地方就去了,去了之后他让网管开一台机器,老板告诉他去40号机器,钱一交就去40号机痛快的打游戏了。打完游戏他一看表,完了时间到了,赶紧跑回去继续执行剩下的计划

这其中他给自己定的这个计划,就相当于我们自己写的一行一行代码,都属于自己的操作,虽然学校里没有地方完成,但是刚入学的时候你已经跟学长问好了,他也暂时不去,但是他知道在哪,这个过程就叫链接,然后执行他的一项一项任务,即我们写的代码,走到打游戏这一步,就跳转过去执行,这个网吧就是我们所说的,问网管的过程就相当于查库的过程,找到你所需要的40号函数,然后执行函数对应的方法,执行完毕再返回继续向后。像这样我们自己编写自己的程序,库一直在那里,只需要在链接的时候把我需要的方法和库中的位置通过地址的方式关联起来,这种方式就叫动态链接

那么也同时存在这样一种情况:并非只有他一个人想打游戏,学校里还可能存在几百上千名躁动的少年,都想在合适的时候去上网,所以那个网吧就是被学校里所有少年共享的一个库。相当于整个系统有成百上千条命令,大家一旦想上网,一旦想去上网都可以使用,这就叫动态链接。

《静态链接》

我的另一个朋友呢,也是一个网瘾少年,只不过他的学校是一个管制不太严的学校,可以带电子设备,又恰好隔壁的网吧生意不景气,要破产卖电脑,于是他就买了一台带进了宿舍。同样他也定了一系列的计划,看书、扣手……打游戏,但是这回他不再需要出去上网,而是下床就直接开始打游戏,他旁边的张三李四等人也都有电脑,他们每一个人上网都是上自己的网,和别人没有关系,调的都是自己的方法,这就叫静态链接

这里把网吧的电脑带到宿舍,就相当于我们把库中的,你所想要的方法拷贝到你的可执行程序当中,这种方法就叫静态。也就是说所有的程序每次有调用这个方法,都会在链接时提前进行拷贝,就都是调用自己的方法了。

动静态库的优缺点:

  • 动态库:全校所有的学生要上网都要来这家网吧,如果有一天,这家网吧突然被警察局查封了,网吧一旦不存在,那全校所有的学生都无法完成这项任务。

    • 所以动态链接的优点:大家共享一个库,可以节省资源

    • 缺点:一旦库缺失,会导致几乎所有程序失效

  • 静态库:将库中的相关代码,直接拷贝到自己的可执行程序当中

    • 优点:只要链接完成,形成可执行程序,不依赖任何库,程序可以独立执行
    • 缺点:浪费资源

动静态库的性质

我们总结一下:

  • 静态库(.a):

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

    • 程序运行的时候将不再需要静态库。

  • 动态库(.so):

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

    • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码

    • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)

    • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

库的制作

测试文件

如下是后文用于测试的头文件和源文件:

其中mymath提供了一个进行累加计算的方法函数

myprint提供了一个打印数字和当前时间戳的函数

这两个用于制作库

test有main函数会调用到这两个函数

mymath.h

#pragma once
#include <cassert>

extern int addToVal(int from, int to);

mymath.cpp

#include "mymath.h"

int addToVal(int from, int to)
{
    assert(from <= to);
    int res = 0;
    for (int i = from; i <= to; i++)
    {
        res += i;
    }
    return res;
}

myprint.h

#pragma once
#include <cstdio>
#include <iostream>
#include <time.h>
using namespace std;

extern void print(int msg);

myprint.cpp

#include "myprint.h"

void print(int msg)
{
    printf("%d: %lld\n", msg, (long long)time(NULL));
}

test.cpp

#include "mymath.h"
#include "myprint.h"

int main()
{
    int res = addToVal(1, 100);
    print(res);
}

产生.o文件:

gcc -c *.c

当前目录的每个.c文件都可以产生它的.o文件

此时再把mymath.omyprint.o直接与test.o链接到一起形成的可执行程序就可以直接运行

g++ test.o mymath.o myprint.o -o test

如上这个链接的过程就是静态链接

静态库

生成静态库

我们把多个.o文件进行打包,形成一个.a文件,这个文件就是静态库

ar -rc libmymath_s.a mymath.o myprint_s.o

<ar是gnu归档工具,rc表示(replace and create) ,也可用来更新一个已存在的库 >

查看静态库的目录列表

ar -tv libmymath.a

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

动态库

生成动态库

动态库所需的.o文件必须用 -fPIC格式生成

g++ -fPIC -c -o mymath_s.o mymath.cpp

g++ -fPIC -c -o myprint_s.o myprint.cpp

g++ -shared -o libmymath.so:mymath_d.o myprint_d.o

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

库的发布

我们知道,一个库的使用需要头文件和库文件

所以,我们把头文件和**.a文件**分别放入lib-static目录中的include和lib目录

头文件libxxx.so文件分别放入lib-dynamic目录中的include和lib目录

为了方便修改发布,我们写个makefile

makefile

#生成动静态库
.PHONY:all
all:libmymath.so libmymath.a

#生成动态库
libmymath.so:mymath_d.o myprint_d.o
	g++ -shared -o $@ $^
mymath_d.o:mymath.cpp
	g++ -fPIC -c -o $@ $^
myprint_d.o:myprint.cpp
	g++ -fPIC -c -o $@ $^
	
#生成动态库
libmymath.a:mymath_s.o myprint_s.o
	ar -rc $@ $^
mymath_s.o:mymath.cpp
	g++ -c -o $@ $^
myprint_s.o:myprint.cpp
	g++ -c -o $@ $^

#生成对应库目录
.PHONY:lib
lib:
	mkdir -p lib-static/lib
	mkdir -p lib-static/include
	cp *.a lib-static/lib
	cp *.h lib-static/include
	mkdir -p lib-dynamic/lib
	mkdir -p lib-dynamic/include
	cp *.so lib-dynamic/lib
	cp *.h lib-dynamic/include

#删除库
.PHONY:clean
clean:
	rm -rf *.o *.a *.so lib-static lib-dynamic

库的使用

上面我们创建了两个库lib-static、lib-dynamic

下面我们写个程序使用一下上面的两个库

用于测试的目录结构:

test.cpp

#include "mymath.h"
#include "myprint.h"

int main()
{
    int res = addToVal(1, 100);
    print(res);
}

链接静态库

如果直接编译,会报错找不到头文件

因为编译器在程序的预处理阶段会在两个位置找头文件

  • 当前路径

  • 系统头文件路径(/usr/include/

链接的阶段会到系统库文件的路径(/lib64/)找库文件

方法一:安装库

所以我们可以把我们的头文件和库文件移到这两个目录

sudo cp lib-static/include/* /usr/include

sudo cp lib-static/lib/* /lib64/

虽然报错信息变了,但还是无法完成编译

此时预处理阶段可以找到头文件了不会报错,

但是在链接阶段,编译器只知道去/lib64/找,但不知道链接哪个库,

那些系统库之所以能被找到,是g++知道那些系统库,我们移入的库g++不认识

所以我们要让g++认识我们的库

g++ test.cpp -lmymath

  • -l:告诉g++我们要链接的库,后面的mymathlibmymath.a掐掉前面的lib去掉后面的.a形成的

此时就链接成功,形成了可执行程序

总结:

如上我们链接一个库共三步:

  • 将头文件移入系统头文件目录
  • 将库文件移入系统库文件目录
  • g++链接时指定库名称

前两步我们称为安装库

但是我们把自己写的头文件和库直接移入系统目录,这无疑会污染系统的头文件、库文件集

所以,我们将把刚刚移入的库和头先移除,再换种方式链接库

sudo rm /usr/include/mymath.h

sudo rm /usr/include/myprint.h

sudo rm /lib64/libmymath.a

这个删除文件的过程就叫卸载库

方法二:g++指定路径

还是只有三个步骤:

  • 让g++知道头文件的路径
  • 让g++知道库文件的路径
  • 让g++知道库文件名

只不过此时是直接告诉g++

g++ test.cpp -o test -I./lib-static/include/ -L./lib-static/lib/ -lmymath

  • -I:添加头文件路径
  • -L:添加库文件路径
  • -l:指定添加的库

链接动态库

编译

与静态库的链接相同,也有两种方法

这里就只用第二种方法演示

g++ test.cpp -o test -I./lib-dynamic/include/ -L./lib-dynamic/lib/ -lmymath

编译g++编译成功了,但是程序运行的时候出错了

这是因为g++在动态链接的时候,我们告诉了g++去哪找那个库,编译成功了,你test后面去哪找这个库跟我g++无关了

但是在程序运行的时候,test进程不知道去哪找这个库

前面我们静态链接的时候运行之所以不会出问题,就是静态链接的会把库的代码拷贝到程序中

但是动态链接的程序不会有库中的代码,在运行程序的时候操作系统需要把库这个文件也加载到内存,如果要执行库的代码块,会跳转到库的代码区执行

所以,我们想办法让进程找到动态库即可,

运行

如下提供了三种方法:

  1. 把动态库拷贝到系统路径下:/lib64/ – 安装

    sudo cp lib-dynamic/lib/libmymath.so /lib64/

    或者在在这个路径下创建动态库的软连接

    sudo ln -s /home/yb/code/linux-learning/test_11_14/uselib/lib-dynamic/lib/libmymath.so /lib64/libmymath.so

  2. 通过导入环境变量的方式 :

    进程运行的时候,在环境变量中查找自己需要的动态库路径 — LD_LIBRARY_PATH

    只需要把我们库的路径追加到LD_LIBRARY_PATH环境变量即可

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/yb/code/linux-learning/test_11_14/uselib/lib-dynamic/lib

    此时ldd test就可以找到库

    程序也可以运行了

  3. 系统配置文件

    一旦我们退出了当前终端,重新打开一个终端

    上面的配置的环境变量就不见了

    在进程概念–>环境变量中我们解释过

    所以,我们就要配置系统文件

    /etc/ld.so.conf.d/目录下有一些文件

    当操作系统找库的时候,除了到系统库目录中查找,还会到这个目录,一个一个读取上面的配置文件,找到动态库

    这些文件的内容也非常简单,就存了库的路径

    所以,我们只需要在当前目录下创建一个.conf文件把动态库的路径存进去就行

    sudo sh -c ‘echo “/home/yb/code/linux-learning/test_11_14/uselib/lib-dynamic/lib” >/etc/ld.so.conf.d/mymath.conf’

    然后使用lsconfig指令,让这个配置文件生效

    sudo ldconfig /etc/ld.so.conf.d/mymath.conf

    此时ldd test就可以找到了库

    程序也可以运行了

    关掉终端重新打开,依然可以运行

动态链接底层原理

这部分设计到进程概念–>Linux进程地址空间的一些东西,下面的内容如果无法理解可以看看这部分

当我我们开始运行test进程,进程控制块task_struct中维护着一个当前进程的虚拟地址空间(用mm_struct管理)

在栈区和堆区中间有一块区域称为共享区

这块虚拟地址空间可以用来指向库文件的代码

当我们把一个程序运行起来,首先会把程序的代码动态库的代码从磁盘加载到物理内存中

虚拟地址空间的正文代码通过页表映射到物理内存的程序代码,共享区函数的代码就会映射到动态库的代码

当我们在代码中调用了printf这样的库函数,调用即跳转到共享区,然后通过页表映射到物理内存执行printf的代码块,执行完之后再跳转回调用printf的下一条指令,继续执行

如果此时又有一个程序被启动,如果它也使用了相同的库,它的共享区也会通过页表映射到相同的物理内存

这便是多个进程共享同一个动态库的底层体现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15691.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java对象内存结构和创建过程

文章目录对象的内存布局对象头Mark WordKlass Pointer实例数据对齐数据对象的创建总结对象的内存布局 我们的对象一般存储在我们的堆内存中&#xff0c;我们把实例对象可以划分为对象头&#xff0c;实例数据&#xff0c;对齐填充 对象头&#xff08;object header&#xff09…

SpringBoot+Vue项目流浪狗领养管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JDK版…

图像运算和图像增强十

图像运算和图像增强十 图像锐化之 Sobel、Laplacian 算子实现边缘检测 &#xff08;1&#xff09;Sobel算子(一阶微分算子) Sobel算子是一种用于边缘检测的离散微分算子&#xff0c;它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值&#xff0c;根据图像边缘旁…

top命令应用(查看进程实时动态信息)

记录&#xff1a;321 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;top命令是查看进程实时动态信息工具。查看进程状态、进程使用内存状况、进程使用CPU状况、进程PID等。 版本&#xff1a; 操作系统&#xff1a;CentOS 7.9 1.top命令介绍 top命令&#xff0c;查看…

内存、指针与数组

C语言的指针可以当成一个特殊的数据类型&#xff08;像int一样的数据类型&#xff09;&#xff0c;可以说其唯一的作用就是为了存储地址&#xff0c;其他的都可以当作它的衍生用法。 指针的诸多功能都是基于其能直接操作指定内存空间存储的值&#xff0c;每个程序运行都会由操作…

git新建仓库提交项目代码+常用命令

一&#xff1a;新建仓库 输入一下仓库名称&#xff0c;归属和路径都是生成的不需要自己去编辑 点击创建就创建了一个新的仓库&#xff0c;下面就是仓库刚创建好的样子 二&#xff1a;向仓库里提交项目代码 首先打开你要提交的项目文件&#xff1a; 根据官方的提示去提交代码&…

Linux:shell编程2(内含:1.设置环境变量+2.位置参数变量+3.预定义变量+运算符+4.条件判断)

写在开头&#xff1a; 小技巧&#xff1a;除了赋值不加空格&#xff0c;其他的&#xff0c;例如是[ ] ()等都需要空格&#xff01; 1.设置环境变量&#xff1a; 注&#xff1a;类似于C语言全局变量 案例1&#xff1a;在/etc/profile文件中定义TOMCAT_HOME环境变量。 解释&…

洛谷 模拟 普及-

文章目录&#x1f4a5;前言&#x1f609;解题报告&#x1f4a5;一、快乐水&#x1f914;一、题意及思路:&#x1f60e;二、源码&#xff1a;&#x1f62e;三、代码分析&#xff1a;&#x1f4a5;二、漂亮的绝杀&#x1f914;一、题意及思路:&#x1f60e;二、源码&#xff1a;&…

小学生python游戏编程arcade----坦克大战2

小学生python游戏编程arcade----坦克大战2前言多摄象头显得分&#xff0c;title地图加载&#xff0c;精灵分层管理&#xff0c;移动精灵1、提示框制作1.1养眼绿色1.2 画距形提示框1.3 效果图1.4 提示框加提示2、子弹计数问题2.1 初始时给一定的子弹量2.2 发射子弹时进行控制2.3…

hevc 半像素

1 分数像素精度运动估计 物体在连续帧间的运动是连续的&#xff0c;而像素本身是离散的&#xff0c;这种现象带来了一个问题&#xff0c;当前帧中图像块的最佳参考块不一定位于参考帧的证书像素点位置&#xff0c;为了更加精确的预测当前带编码的图像块&#xff0c;有必要在非整…

海运整柜出口操作流程有哪些注意事项?

货物运输时&#xff0c;海运是一种非常常见的形式&#xff0c;根据货物的不同&#xff0c;海运也有很多形式的货物装运&#xff0c;海运整柜就是其中之一。 海运整柜大致分为20GP/40/GP/40HQ。是指只有一个发货人将整箱货物运到目的港&#xff0c;比较容易竞争。发货人负责装箱…

IntentService 源码理解

一、概述 本篇文章讲解的是分析IntentService源码并使用&#xff0c;安卓API迭代更新的太快&#xff0c;IntentService已经在Android8.0 (API 26)之后就不推荐使用了&#xff0c;在Android API 30正式弃用&#xff0c;官方建议用JobIntentService 或 WorkManager替代&#xff0…

为什么要少用全局变量

为什么要少用全局变量&#xff1f;甚至有些公司禁止用全局变量。有一个说法是这样的&#xff0c;全局变量的最佳前缀是什么&#xff1f;答&#xff1a;// 接下来就粗略说说这个问题。 1、全局变量和局部变量 &#xff08;1&#xff09;全局变量&#xff1a;定义在函数外&…

RocketMQ NameServer 概览

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2022年11月18日 &#…

析构函数详解

析构函数1.概念与特性2.工作原理4.析构的顺序如果一个类中什么成员都没有&#xff0c;那么该类简称为空类。而空类中其实并不是真的什么都没有&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。构造函数&#xff1a;主要完成初始化工作析构函…

内网渗透神器CobaltStrike之配置与基础操作(一)

CobaltStrike简介 Cobalt Strike: C/S架构的商业渗透软件&#xff0c;适合多人进行团队协作&#xff0c;可模拟APT做模拟对抗&#xff0c;进行内网渗透。 Cobalt Strike 一款GUI的框架式渗透工具&#xff0c;集成了端口转发、服务扫描&#xff0c;自动化溢出&#xff0c;多模…

megahit源码迁移解析

megahit源码迁移大作业 在进行megahit源码迁移前需要清理自己的实验环境 1、链接鲲鹏服务器 2、进入源码存放地址/opt/portadv/portadmin/sourcecode 环境准备&#xff0c;清理之前实验环境后下载本次实验所需的源码 之前存在的其他文件&#xff0c;删除code 登录代码迁移工…

Python常见操作的时间复杂度

Python常见操作的时间复杂度 本文整理了Python中常见数据结构操作的时间复杂度&#xff0c;旨在帮助大家了解Python操作的性能&#xff0c;协助运行更快的代码。 文章目录标注方法List操作Set操作Deque操作标注方法 程序时间复杂度一般用"大O表示法&#xff08;Big-O no…

windows11系统WSL2安装ubuntu20.04桌面

文章目录1. MobaXterm安装2.WSL安装xfce desktop3. 连接桌面参考链接1. MobaXterm安装 这个比较简单&#xff0c;没介绍 2.WSL安装xfce desktop 安装命令 sudo apt-get install xfce4-terminal sudo apt-get install xfce4安装完之后需要稍微配置一下&#xff1a; export …

【并发编程六】c++进程通信——信号量(semaphore)

【并发编程六】c进程通信——信号量&#xff08;semaphore&#xff09;一、概述二、信号量三、原理四、过程1、进程A过程2、进程B过程五、demo1、进程A2、进程B六、输出七、windows api介绍1. 创建信号量 CreateSemaphore()2. 打开信号量 OpenSemaphore()3. 等待 WaitForSingle…