Linux | 编译和使用自定义动静态库的全面解析

news2024/11/15 12:24:19

新竹高于旧竹枝,全凭老干为扶持。 - 《新竹》(郑燮)

2024.8.25

目录

1、静态库和动态库简介

静态库(.a):

动态库(.so):

动态库和静态库的比较:

2、静态库的生成和使用(Static Library)

编写头文件声明库函数并编译生成目标文件(.o)

使用ar命令将目标文件打包成静态库

编译使用静态库的程序文件

使用makefile自动构建工具构建静态库

使用静态库

安装库

使用库

运行使用库的应用程序

注意事项

3、动态库的生成和使用(Dynamic Library)

编写头文件声明库函数并编译生成目标文件(.o)

编译源代码为对象文件:

使用gcc将目标文件打包成静态库打包成动态库:

编译使用动态库的程序并运行:

后记:理解程序的编译、执行与库函数调用机制

编译过程:从源代码到可执行文件

编译器的角色

动态库的加载与执行

静态库与动态库的区别


在Linux系统中,库(Library)是一种重要的组件技术,它封装了数据和函数供用户程序调用。库主要分为静态库和动态库两种类型,它们在编译、链接和运行时有不同的特点和使用方式。

1、静态库和动态库简介
静态库(.a)
    • 静态库是一种编译时链接的库,它在编译期间将库中的代码复制到最终的可执行文件中。
    • 这意味着静态库的代码在编译时就已经被包含在可执行文件中,运行时不再需要静态库文件。
    • 优点是可执行文件独立,可以在没有静态库文件的情况下运行。
    • 缺点是可执行文件体积较大,因为包含了库的所有代码。
动态库(.so)
    • 动态库是一种运行时链接的库,它在程序运行时才被加载和链接。
    • 动态库的代码不会被复制到可执行文件中,可执行文件只包含对动态库中函数的引用。
    • 优点是可以被多个程序共享,节省了磁盘空间,并且可以方便地更新库而不需要重新编译程序。
    • 缺点是程序运行时需要动态库文件,如果库文件缺失或版本不兼容,程序可能无法运行。
动态库和静态库的比较:
  • 载入顺序:静态库在编译时链接,动态库在运行时链接。
  • 大小与共享:静态库生成的可执行文件较大,不共享;动态库生成的可执行文件较小,可共享。
  • 库函数调用:静态库直接包含代码,动态库在运行时解析函数地址。

选择使用静态库还是动态库取决于具体的应用场景和需求。静态库适合于不需要频繁更新,且对可执行文件大小不敏感的场景;而动态库适合于需要共享库代码,或者库更新频繁的场景。

2、静态库的生成和使用(Static Library)

静态库在编译时将库代码嵌入到可执行文件中,生成的可执行文件较大,但运行时不再需要依赖库文件。静态库的文件后缀名为.a,例如libmylib.a

我们现在希望创建一个属于自己的库,实现加减乘除四个函数创建静态库的步骤大致包括:

编写头文件声明库函数并编译生成目标文件(.o

将add.c / sub.c / mul.c / div.c 通过gcc生成目标 .o 文件(gcc默认生成同名.o文件)

gcc -c add.c sub.c mul.c div.c
使用ar命令将目标文件打包成静态库
ar -rcs libmymath.a add.o sub.o mul.o div.o

👋

  • ar:静态库创建工具。
  • rcs:选项的含义如下:
    • r:替换已存在的库中的文件或插入新文件。
    • c:创建库时不显示成员文件信息。
    • s:创建索引,便于快速访问库中的成员文件。
  • libmymath.a:你想要创建的静态库文件名。
  • add.osub.o:你想要包含在库中的目标文件。
编译使用静态库的程序文件

通过gcc-l选项指定库名(不包括lib前缀和.a后缀),并使用-L选项指定库文件的路径。

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

使用makefile自动构建工具构建静态库
lib = libmymath.a
libdir = mymathlib

$(lib):add.o sub.o
    ar -rcs $@ $^
add.o:add.c
    gcc -c $^
sub.o:sub.c
    gcc -c $^
mul.o:mul.c
    gcc -c $^
div.o:div.c
    gcc -c $^

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

.PHONY:bag
bag:
    mkdir -p lib/include
    mkdir -p lib/$(libdir)
    cp *.h lib/include
    cp *.a lib/$(libdir)

打包完成后,将lib目录压缩即可发布属于自己的静态包~~

使用静态库

您提供的 Makefile 片段定义了一个名为 libmymath.a 的静态库的构建过程,以及一些清理和组织库文件的规则。要使用这个静态库,您需要遵循以下步骤:

测试程序:

#include<stdio.h>
#include<add.h>
#include<sub.h>

int main()
{
    int a,  b;
    scanf("%d %d", &a, &b);
    printf("a + b = %d\n", add(a,b));
    printf("a - b = %d\n", sub(a,b));

    return 0;
}

安装库

这将创建 lib/includelib/mymathlib 目录,并将头文件(*.h)复制到 lib/include,将库文件(*.a)复制到 lib/mymathlib

使用库

要使用这个库,您需要在编译您的应用程序时指定库的头文件路径和库文件路径。假设您的应用程序 main.c 使用了这个库,您可以这样编译它:

gcc -I./lib/include -L./lib/mymathlib -o main main.c -lmymath

这里的命令参数解释如下:

  • -I./lib/include:告诉编译器在 lib/include 目录下查找头文件。
  • -L./lib/mymathlib:告诉链接器在 lib/mymathlib 目录下查找库文件。
  • -o main:指定输出的可执行文件名为 main
  • -lmymath:告诉链接器链接名为 mymath 的库(注意 .a 后缀和 lib 前缀被省略)。
运行使用库的应用程序
./main
注意事项
  • 确保在编译和链接应用程序时,库文件和头文件的路径是正确的。
  • 我们已经您将库安装到了系统的标准目录,不需要 -L-I 选项,因为编译器和链接器会自动在这些目录下查找,但是不建议将自己写的库放在标准目录(环境变量覆盖的)目录下。

3、动态库的生成和使用(Dynamic Library)

创建动态库是一个涉及编译和链接过程的任务,通常使用C语言进行。以下是创建包含add.csub.c两个函数文件的动态库的基本步骤:

编写头文件声明库函数并编译生成目标文件(.o
    • add.c 包含一个函数,比如 int add(int a, int b),用于计算两个整数的和。
    • sub.c 包含一个函数,比如 int sub(int a, int b),用于计算两个整数的差。
    • mul.c 包含一个函数,比如 int mul(int a, int b),用于计算两个整数的乘积。
    • div.c 包含一个函数,比如 int div(int a, int b),用于计算两个整数的商。
编译源代码为对象文件

使用编译器(如gcc)编译每个源文件,生成对应的对象文件(.o)。

gcc -c *.c
使用gcc将目标文件打包成静态库打包成动态库

将编译后的对象文件链接成动态库。在Unix-like系统中,通常使用-shared-fPIC(Position Independent Code)选项来创建动态库。

gcc -shared -o libmymath.so *.o

这里libmymath.so是生成的动态库文件名,你可以根据需要更改它。

编译使用动态库的程序并运行

编译时不需要链接动态库,只需在运行时加载。

gcc main.c -L 存放库的目录 -I存放头文件的目录 -lmymath -o main

可执行程序能够正常生成,但运行可执行程序时出现报错:

error while loading shared libraries:
 libmymath.so: cannot open shared object file:
 No such file or directory

原因是动态库必须在系统默认路径下存在库和头文件的软链接才能在运行时找到库和头文件中的定义。

建立软链接:

sudo ln -s /home/lhy/linux/05基础IO/动态库/dir/libmymath.so /lib/libmymath.so
sudo ln -s /home/lhy/linux/05基础IO/动态库/dir/mymath.h /usr/include/mymath.h

注意:不同系统默认存放库和头文件的路径不同,我这里是Ubuntu系统,库的路径是/lib,头文件的默认路径是/usr/include(当时上网找教程找的是centos系统的,路径和系统不同,后面才发现哈哈哈,大家注意避坑~~)

解决了软链接的问题,程序就能正常运行啦~~

后记:理解程序的编译、执行与库函数调用机制
编译过程:从源代码到可执行文件

程序的生命周期始于源代码,终于可执行文件。源代码是程序员用高级语言编写的指令集合。通过编译器,这些指令被转换成汇编语言,再进一步转换成机器码。在这一过程中,每个指令都被赋予了一个逻辑地址,这个地址是程序在内存中的虚拟地址。

逻辑地址是程序地址空间中的一个抽象概念,它与物理内存地址相对应,但并不直接映射。这种设计允许操作系统灵活地管理内存,实现虚拟内存技术。

编译器的角色

编译器不仅负责语言的转换,还负责生成程序的入口地址,这个地址存储在可执行文件的文件头中。它是程序执行的起点,也是CPU开始执行指令的地方。

CPU通过程序计数器(PC)寄存器来追踪当前需要执行的指令地址。当程序加载到内存后,操作系统会建立页表,将逻辑地址映射到物理地址。CPU使用这些映射来找到并执行指令。

动态库的加载与执行

动态库是程序执行中不可或缺的一部分。它们提供了程序所需的函数和资源,但是与传统的理解不同,动态库并不需要固定加载到内存的特定位置。

在动态库内部,不采用绝对编址,而是采用相对编址,对于动态库中的函数只需要知道其在库中的偏移量即可。这意味着库中的每个函数都有一个相对于库起始位置的偏移量。这样,无论库被加载到内存的哪个位置,只要加上这个偏移量,就能找到正确的函数地址。

这就是为什么,在使用gcc生成动态库的时候,需要加上-fPIC选项用于生成位置无关代码,这对于动态库尤其重要。它允许动态库在内存中灵活加载,而不需要在程序启动时确定其位置。

静态库与动态库的区别

静态库在编译时被复制到可执行文件中,使用绝对编址。这意味着静态库的大小会直接影响可执行文件的大小。而动态库则在程序运行时按需加载,节省了磁盘空间和内存。

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

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

相关文章

GCB | 首次揭示!气候变化对常绿和落叶植物物候差异化影响的机制

气候变化引起的植物物候改变已对全球范围内生物多样性和生态系统产生重大影响&#xff08;Nature高引文章 | 北京大学朴世龙院士等&#xff1a;全球变暖对植被物候的影响及其机制&#xff1b;Nature Ecology & Evolution | 南京大学张永光教授团队揭示延长的植被物候期受CO…

set容器的所有操作

1.基本概念 2.构造和赋值 3.大小和交换 4.插入和删除 5.查找和统计 6.set和multiset的区别 7.pair对组创建 用p.first和p.second调用前后两个属性。 8.仿函数实现降序排列 自定义数据类型也一样用仿函数&#xff1a;

【领域驱动设计 打通DDD最小闭环】领域建模

本篇BLOG为DDD流程的第二步&#xff0c;在模型的建立阶段&#xff0c;领域专家与技术人员通过领域建模来完成更为细致的模型建立讨论 领域建模的目的 领域建模主要有两个目的&#xff1a; 将知识可视化&#xff0c;准确、深刻地反映领域知识&#xff0c;并且在业务和技术人…

Python优化算法14——海鸥优化算法(SOA)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

【图文并茂】ant design pro 如何给后端发送 json web token - 请求拦截器的使用

上一节有讲过 【图文并茂】ant design pro 如何对接后端个人信息接口 还差一个东西&#xff0c;去获取个人信息的时候&#xff0c;是要发送 token 的&#xff0c;不然会报 403. 就是说在你登录之后才去获得个人信息。这样后端才能知道是谁的信息。 token 就代码了某个人。 …

工作实战-项目压测记录

1-1-1每分钟的单量 1-1-2第二版测试 2022年5月16日 17:43:11 成功 失败 其它(nginx) 真实入库单量 总单量 52 1 447 500 2022年5月16日 19:42:18 成功 失败 其它(nginx) 真实入库单量 总单量 311 689 306 1000 2-0-1. 20线程-2000单执行结果 2-1-0. 40线…

金融科技 API 接口:提升金融服务效率的关键

金融科技是应用技术手段和创新理念来提升金融服务效率的重要途径。而其中的API接口则是实现金融科技的关键。API接口的简单定义是提供计算机程序之间通信的规范和工具&#xff0c;提供一种方法和数据的交互形式&#xff0c;以便开发人员能够利用现有的软件来创建新的应用和服务…

前端网格布局display: grid;

display: grid; 块级网格 &#xff08;常用&#xff09; display: inline-grid; 行内块级网格 &#xff08;一般不用&#xff09; HTML 元素将 display 属性设置为 grid 或 inline-grid 后&#xff0c;它就变成了一个网格容器&#xff0c;这个元素的所有直系子元素将…

Mobile-Agent项目部署与学习总结(DataWhale AI夏令营)

前言 你好&#xff0c;我是GISer Liu&#xff0c;一名热爱AI技术的GIS开发者&#xff0c;本文是DataWhale 2024 AI夏令营的最后一期——Mobile-Agent赛道&#xff0c;这是作者的学习文档&#xff0c;这里总结一下&#xff0c;和作者一起学习这个多模态大模型新项目吧&#x1f6…

开放式耳机对耳朵的伤害会减小吗?亲测好用的四款蓝牙耳机

开放式耳机对耳朵的伤害相对较小。 首先&#xff0c;开放式耳机不入耳&#xff0c;不会直接堵塞耳道&#xff0c;减少了对耳道的挤压和摩擦&#xff0c;降低了因长期佩戴入耳式耳机可能导致的耳道发炎、疼痛等问题。 其次&#xff0c;由于耳朵没有被完全封闭&#xff0c;耳部能…

【C/C++】结构体指针赋值的方法

在C语言中&#xff0c;结构体指针赋值有两种方法&#xff1a; 1.使用"->"操作符&#xff1a;通过结构体指针访问结构体成员并赋值。例如&#xff1a; struct Student {int id;char name[20]; };int main() {struct Student s1;struct Student *ptr_s1 &s1;…

设计模式学习[4]---依赖倒置原则+里氏代换原则

文章目录 前言1. 依赖倒置原则1.1 原理阐述1.2 举例 2.里氏代换原则2.1 原理阐述2.2 举例说明 总结 前言 在给发新功能模块之前&#xff0c;都先要画UML类图进行需求分析。对每一个类的作用&#xff0c;类与类之间的关系做一个详细的阐述。确定好基本的框架之后&#xff0c;才…

Lumos学习王佩丰Excel第十四讲:日期函数

一、认识时间和日期 1、回顾日期格式 日期可以由数字表示。 2、时间格式 时间由小数表示。 3、基本的时间与日期运算 二、日期函数 1、Year、Month、Day函数 2、Date函数 DATE(year,month,day&#xff09;&#xff1a;year为必需参数&#xff0c;可以包含1到4个数字。Day函…

LeetCode 热题 100 回顾

目录 一、哈希部分 1.两数之和 &#xff08;简单&#xff09; 2.字母异位词分组 &#xff08;中等&#xff09; 3.最长连续序列 &#xff08;中等&#xff09; 二、双指针部分 4.移动零 &#xff08;简单&#xff09; 5.盛最多水的容器 &#xff08;中等&#xff09; 6…

文心一言 VS 讯飞星火 VS chatgpt (332)-- 算法导论23.1 1题

一、设(u,v)是连通图G中的一条权重最小的边&#xff0c;证明&#xff1a;边(u,v)为图G的某棵最小生成树中的一条边。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 为了证明边(u,v)是图G的某棵最小生成树中的一条边&#xff0c;我们可以使用反证法结合最小生成…

09 复合查询

前面的查询都是对一张表进行查询&#xff0c;但这远远不够 基本查询回顾 查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J select * from EMP where (sal>500 or job‘MANAGER’) and ename like ‘J%’; 按照部门号升序而雇员的…

免费用的写作神器:一键自动生成文章,提升内容创作效率!

传统的写作过程往往耗时耗力&#xff0c;从构思、调研到撰写、修改&#xff0c;每一步都需要创作者亲力亲为。免费写作神器的出现&#xff0c;犹如一场及时雨&#xff0c;为创作者提供了强大的支持。它利用先进的算法和自然语言处理技术&#xff0c;能够在短时间内生成高质量的…

了解Redis数据持久化(中)

3.5写时复制 Redis在使用RDB方式进行持久化时&#xff0c;会用到写时复制机制。写时复制的效果: bgsave子进程相当于复制了原始数据&#xff0c;而主线程仍然可以修改原来的数据。 对Redis来说&#xff0c;主线程fork出bgsave子进程后&#xff0c;bgsave子进程实际是复制了主线…

初识C语言指针(5)

目录 1. 回调函数 2. qsort函数 2.1 qsort函数的基本参数 2.2 qsort函数的使用 2.3 qsort排序结构体类型数据 结语 1. 回调函数 什么是回调函数呢&#xff1f;回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另…

超分CAMixerSR 使用笔记

目录 超分CAMixerSR 笔记 自己改进的图例示例: 修改目录: 设置预训练模型: 超分CAMixerSR 笔记 自己改进的图例示例: 修改目录: codes/basicsr改为codes/basicsr_m 设置预训练模型: path:pretrain_network_g: F:\project\chaofen\CAMixerSR-main\pretrained_mode…