从0开始linux(26)——动静态库

欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪

文章目录

    • 如何写一个静态库
    • 动态库
    • 动静态链接

c/c++程序形成可执行程序,需要经过三个步骤,编译、汇编、链接三个步骤,我们之前做链接时,使用的方法是将头文件、实现头文件方法的.c文件以及主文件(即包含main函数的文件)放在同一个目录下,这样子就能让多个.c文件链接,形成一个可执行文件。

但是我们的程序通常使用到库函数,而库函数的实现文件其实不会跟我们的主文件放在同一个目录下进行链接的。那么为什么我们可以轻松的与标准库进行链接呢?

首先标准库的头文件通常会在/usr/include路径下,这是linux系统的默认标准库的头文件路径。我们常用的stdio,stdlib文件都在该路径下。
在这里插入图片描述
所以我们经常在包含标准库的头文件时,通常是使用<>,而非"",这是因为我们使用<>包含的头文件,都是去/usr/include路径下查看。而\“”\”包含的头文件,都是去同一路径下寻找头文件。

而与包含头文件的程序进行链接的,也不是什么.c文件,而是标准库封装的动态库(也有静态库,但是默认情况下是与动态库链接)。C语言标准库的路径是/usr/lib64/libc.so。

在这里插入图片描述

这是一个简单的c程序

#include<stdio.h>int main()
{printf("hello world\n");
}

我们编译该程序时,实际上gcc编译器会先到/usr/include/stdio.h中检查我们包含的stdio.h文件,接着链接/usr/lib64/libc.so动态库。最后形成可执行程序。

那么知道了原理,我们是不是也可以写一个自己的库?首先库分为静态库和动态库,它们的链接形式不同,我们先从静态库开始

如何写一个静态库

我们仿着<string.h>写一个<mystring.h>。也就是写一个针对字符串处理的库。首先是我们的头文件<mystring.h>。其代码如下:

#pragma oncetypedef unsigned int uint;
uint my_strlen(const char* str);
char* my_strcpy(char* dest,const char* src);
int my_strcmp(const char* lhs,const char* rhs);

接着我们将头文件拷贝到/usr/incliude/路径下。

cp mystring.h /usr/include/mystring.h

接着写一个实现<mystring.h>的文件<mystring.c>。

#include"mystring.h"uint my_strlen(const char* str)
{int cnt=0;while((*str)!='\0'){str++;cnt++;}return cnt;
}char* my_strcpy(char* dest,const char* src)
{char* cur=dest;while(*cur=*src){cur++;src++;}return dest;
}int my_strcmp(const char*lhs,const char*rhs)
{while(*lhs++==*rhs++){if(*lhs=='\0'||*rhs=='\0')break;}return *lhs-*rhs;
}

我们将该.c文件编译生成.o文件,因为无论是动态库,还是静态库,其实都是将编译好的.o文件打包在一起。

gcc -c myystring.c

在这里插入图片描述
接着我们是用ar -rc指令将.o文件形成静态库,要注意,静态库是有文件格式的,一个静态库的格式为lib[libname].a,即库名前要有前缀lib,以及后缀.a。我们将这个打包好的静态库命名为:mylib。因此打包静态库的指令为:

ar -rc libmylib.a mystring.o

在这里插入图片描述
接着我们将生成好的静态库移动到系统默认的库的路径下。即/usr/lib64/路径下

mv libmylib.a /usr/lib64/libmylib.a

现在属于我们自己的库就已经写好了。我们来写一个程序试试用一下吧。

#include<mystring.h>//我们包头文件时用的是<>
#include<stdio.h>int main()
{const char* str1="hello world";char str2[255]={0};my_strcpy(str2,str1);//mystring.h当中的函数printf("%s\nstrltn:%d\n",str2,my_strlen(str2));//mystring.h的函数if(my_strcmp(str2,str1)==0)//mystring.h的函数{printf("str1==str2\n");}return 0;
}

接着我们编译该程序。

gcc test.c -o test

此时我们惊讶的发现,这个程序的编译竟然不通过
在这里插入图片描述
通过分析,我们发现编译代码时发生了链接错误,诶?我们不是把静态库放在了/usr/lib64/吗?按道理gcc编译器会在这个默认路径下帮我们链接的。为什么没有呢?这是因为gcc会在该默认路径下找库进行链接,但是它只认识标准libc.a。不认识我们的libmylib.a。因此我们要指明,gcc编译时,要去用libmylib.a进行链接。指令为-l [libname]。我们的[libname]是mylib,因此正确的指令为:

gcc test.c -o test -l mylib

在这里插入图片描述

动态库

动态库生成不用ar指令将.o文件打包起来,而是使用gcc将方法文件(.c文件)集成生成动态库。动态库的命名形式为:lib[libname].so,即前缀是lib,后缀为.so。

让gcc将文件编译生成动态库的选项为-shared以及-fPIC。因此正确指令如下

gcc mystring.c -o libmylib.so -shared -fPIC

在这里插入图片描述
我们接着将动态库移动到/usr/lib64路径下。接着编译test.c

gcc test.c -o test -l mylib

在这里插入图片描述
这时候有人可能就会问了,我记得/usr/lib64目录下不是还有一个libmylib.a的静态库吗?我怎么确定链接的是动态库而不是静态库。

我们要注意,使用gcc编译时,如果同一路径下存在静态库和动态库,默认链接的是动态库,而非静态库,如果我们非要链接静态库,需要加上-static选项。比如我们用相同代码的test.c,通过链接静态库的形式生成可执行程序test2。看看这两个程序有何不同。

gcc test.c -o test2 -static -l mylib

在这里插入图片描述
我们发现,静态库链接的test2程序的大小足足是动态链接的test程序的100倍!可是它们的源代码明明是一样的,为什么链接结果差距这么大。这就要聊聊动静态库的特性了

动静态链接

与静态库链接的程序我们称为静态链接,静态链接的原理,是将静态库中实现的方法,直接拷贝到程序当中,因此程序的大小会比较大,但是由于方法都拷贝到了程序中,因此程序的依赖性比较小。

而动态链接的程序,其原理并非是将动态库中的方法链接到程序当中,而是将方法的地址替换掉程序中调用动态库方法的代码,当执行到该代码时,进程会跳转到动态库中,在动态库执行方法,因此动态链接的程序的大小比较小,但是由于进程需要跳转到动态库当中,因此一旦动态库丢失或者发生损坏,链接该动态库的一切程序都会失效。

test是与libmylib.so链接的进程,当我们打开test进程时,操作系统会在内存当中创建一个对应的PCB(task_struct)。以及进程的地址空间(mm_struct),映射进程地址空间与内存空间的页表。如下图:
在这里插入图片描述
而test所链接的动态库,其实是会将动态库的地址,保存在共享区当中,因此当test进程被打开时,test所使用的所有动态库,都会被加载到内存当中。
在这里插入图片描述
当test进程,执行到libmylib.so库中的函数时,会跳转到libmylib.so当中,当函数执行完成时,会带着运行结果,又回到test进程当中,因此,如果我们将libmylib.so删除,那么当test运行时,动态库无法加载到内存当中,执行动态库中的函数时,也无法跳转到动态库上,因此与动态库链接的所有进程,都将无法运行。

由于动态库与进程息息相关,因此博主打算放在下一个章节当继续讲解进程与动态库之间的关系,以及它们的链接原理。

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

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

相关文章

hexo 搭建个人博客网站

hexo搭建个人博客 文章目录 hexo搭建个人博客摘要搭建网站的前置工具WebStormHexo Hexo配置初始化本地运行 主题更改安装butterfly主题 正式上线GitHub Pages配置新建仓库SSH密钥配置 将hexo部署到GitHub 个性化设置后续网站更新内容分类和标签设置分类&#xff08;categories&…

BLDC基础知识复习【二】

如果采用20毫欧的电流采样电阻&#xff0c;10A的电流计算出来时0.2V&#xff0c;这个显然还是太小了&#xff0c;需要运放放大并且加上偏置&#xff1a; 6组换向程序&#xff1a; 最核心的控制逻辑在这里&#xff1a;在main.c里面对PWM占空比进行设置&#xff0c;通过一个指针在…

1130 - Host ‘10.0.0.1‘ is not allowed to connect to this MySQL server

1130 - Host 10.0.0.1 is not allowed to connect to this MySQL server 一、1130 - Host 10.0.0.1 is not allowed to connect to this MySQL server二、1130 - Host 10.0.0.1 is not allowed to connect to this MariaDB serverendl 一、1130 - Host ‘10.0.0.1’ is not all…

构建智慧城市:数字孪生技术的发展之路

基于数字孪生的智慧城市发展是一种革命性的城市转型模式&#xff0c;旨在将物理世界与数字世界融合&#xff0c;在数字平台上建立城市的虚拟映像&#xff0c;从而实现对城市运行状态、资源利用、环境影响等方面的综合管理和优化。这种发展模式将数字技术深度融入城市规划、建设…

金融行业信息流投放方法论及金融客户投放案例

失血2024&#xff0c;金融行业进入“极寒”&#xff0c;广告投放也不例外。 受金融政策管控&#xff0c;在渠道投放受限也颇多&#xff0c;创意文案及素材上审核异常严格&#xff0c;整体投放成本高…… 金融理财信息流广告投放&#xff0c;如带着“镣铐”跳舞&#xff0c;束…

Unity-Yaml-Dot-Net诗歌篇-如何像雷总学习写代码像诗歌-MVC 框架,+注入Inject +状态机生命周期

我们是否可以像雷总一样 大家都说他的代码&#xff0c;像诗一样优雅 一个MVC 框架&#xff0c;加注入 &#xff08;以下内容其实和雷总没什么关系&#xff0c;也和雷总当年代码毫无关系&#xff0c;不过先“阅读理解”一下&#xff09; 雷总-写的代码像似一个优雅??!!^^ R…

安卓好软-----电脑端查看apk全部信息的工具 查看包名 名称以及权限等等

有时候从网络下载的应用很多是英文。时间久了会忘记到底是什么apk应用。这款工具可以方便的查看apk应用的名称 包名以及各种权限 图标 大小版本号等等。方便用户随时查看 APK Helper能够详细地获得安装包名、软件名称、APK证书、真实版本号、要求的手机版本、系统权限、以及证书…

分布式唯一ID生成(四):tinyid

文章目录 本系列前言号段模式多DB支持tinyid-client 本系列 漫谈分布式唯一ID分布式唯一ID生成&#xff08;二&#xff09;&#xff1a;leaf分布式唯一ID生成&#xff08;三&#xff09;&#xff1a;uid-generator分布式唯一ID生成&#xff08;四&#xff09;&#xff1a;tiny…

JavaWeb:文件上传2

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

April tag坐标定义

朝右的方向为x轴正向&#xff0c; 朝下的方向为y轴正方向&#xff0c; z 轴垂直纸面向里。 4个角点的坐标定义如下图所示。

江行智能×图扑软件:输煤皮带数字孪生管控系统

在现代矿业和电力行业中&#xff0c;输煤皮带系统是运输环节的核心。然后&#xff0c;随着智能化生产的高要求&#xff0c;智慧矿山输煤皮带系统的传统管理模式已显得力不从心。产业引入的三维可视化和数字孪生技术&#xff0c;一改传统输煤皮带系统由于设备老化、管理不善等原…

【Python】从入门开始抓取你想要的电影,一周可掌握基础,附完整源码

Python学习很简单&#xff0c;只是你走进了误区。 为什么你一定要先掌握枯燥的基础点后&#xff0c;再去做实际操作呢&#xff1f; 其实&#xff0c;你根本坚持不了那么长时间&#xff0c;但实际上你可以直接去做python项目。 不信&#xff1f;看看我做这个项目的思路&#x…

【C++】map和set的介绍及使用

前言&#xff1a; map和 set 是 C STL&#xff08;标准模板库&#xff09;中的两种非常重要的容器&#xff0c;它们基于一种叫做平衡二叉搜索树&#xff08;通常是红黑树&#xff09;的数据结构来实现。在 C 中&#xff0c;map 是一个键值对容器&#xff0c;set 只存储唯一的键…

NumPy 数组属性

1.NumPy 数组的基本属性 NumPy 数组的维数称为秩&#xff08;rank&#xff09;&#xff0c;秩就是轴的数量&#xff0c;即数组的维度&#xff0c;一维数组的秩为 1&#xff0c;二维数组的秩为 2&#xff0c;以此类推。NumPy中&#xff0c;每个线性的数组称为轴&#xff08;axis…

Spring源码(十二):Spring MVC之Spring Boot

本篇将详细讨论Spring Boot 的启动/加载、处理请求的具体流程。我们先从一个简单的Spring Boot项目日志开始分析&#xff08;这里假设读者已经仔细阅读完了前面的文章&#xff0c;且对Spring源码有一定深度的了解&#xff0c;否则会看得一脸懵逼&#xff09;。 本文为2024重置…

游戏引擎学习第四天

视频参考:https://www.bilibili.com/video/BV1aDmqYnEnc/ BitBlt 是 Windows GDI&#xff08;图形设备接口&#xff09;中的一个函数&#xff0c;用于在设备上下文&#xff08;device context, DC&#xff09;之间复制位图数据。BitBlt 的主要用途是将一个图像区域从一个地方复…

双指针算法的妙用:提高代码效率的秘密(2)

双指针算法的妙用&#xff1a;提高代码效率的秘密&#xff08;2&#xff09; 前言&#xff1a; 小编在前几日讲述了有关双指针算法两道题目的讲解&#xff0c;今天小编继续进行有关双指针算法习题的讲解&#xff0c;老规矩&#xff0c;今天还是两道题目的讲解&#xff0c;希望…

[CKS] K8S NetworkPolicy Set Up

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于不安全项目修复的题目。 What’s the NetworkPolicy 关于network policy的介绍可以查看&#xff1a; https://kubernetes.io/docs/concepts/services-networking/network-policies/ Question 1 …

python全栈开发《62.获取两个集合的并集》

目录 1.什么是并集2.union的功能3.union的用法4.代码 1.什么是并集 集合a&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4 集合b&#xff1a;3&#xff0c;4&#xff0c;5&#xff0c;6 a和b一共拥有的不重复的元素有1&#xff0c;2&#xff0c;3&#xff0c;4&#xff…

DICOM图像知识:DICOM图像排序与坐标系解析

目录 引言 1. 概述 2. DICOM图像排序规则 2.1 Patient的Study按Study Date排序 2.2 Study的Series按Series Number排序 2.3 Series的SOP按Instance Number或Slice Location排序 2.3.1 Instance Number排序 2.3.2 Slice Location排序 2.3.3 使用Image Position (Patien…