Linux —— 动态库和静态库

https://blog.csdn.net/sjsjnsjnn/article/details/125836184

目录

一、认识动静态库 

二、回顾编译链接的过程

三、库的制作和使用 

1.静态库的制作  

1.生成二进制(.o)文件

2.打包

3.发布静态库

2.静态库的使用

方法一 

方法二

3.动态库的制作 

1.生成二进制(.o)文件

2.打包

3.发布动态库

4.动态库的使用

方法一

方法二 

四、动态库与静态库特点总结


一、认识动静态库 

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

 我们可以通过 [ ldd 可执行程序文件名 ]来查看可执行程序所依赖的库。

其中 /lib64/libc.so.6 就是可执行程序的库文件,它其实是一个软链接。我们可以通过以下命令来查看

[mlg@VM-20-8-centos lesson5-动静态库]$ ls /lib64/libc.so.6 -l

如何辨别它采用的是哪一种库呢?

1.我们可以通过后缀来区分(在Linux中)

        在Linux中,以 .so 结尾的后缀,是动态库;以 .a 结尾的是静态库  

        在Windows中,以 .dll 结尾的后缀,是动态库;以 .lib 结尾的是静态库  

注:

        库文件的名字:libxxx.so 和 libxxx.a

        库的真实名字:去掉lib前缀,去掉 .a 、.so后缀,剩下的就是库的名称。

2.我们还可以通过file命令查看

        上图中分别使用了动静态库对同一个文件进行编译的,通过file命名可以查看到所对应的链接信息,如果为安装静态库,是编译不通过的,具体安装教程可以点这里Linux——环境基础开发工具的使用

二、回顾编译链接的过程

在Linux中,gcc的编译可以分为一下四个步骤:

  • 预处理:在预处理阶段主要负责的是头文件的展开、去掉注释、宏替换、条件编译等。以#号开头的是预处理指令:#define #if #include...... 此阶段产生【.i文件】
gcc -E mytest.c -o test.i
  • 编译 此阶段完成语法和语义分析,然后生成中间代码,此中间代码是汇编代码,但是还不可执行,gcc编译的中间文件是[.s]文件。在此阶段会出现各种语法和语义错误,特别要小心未定义的行为,这往往是致命的错误。
gcc -S test.i -o test.s
  • 汇编:此阶段主要完成将汇编代码翻译成机器码指令,并将这些指令打包形成可重定位的目标文件,[.o]文件,是二进制文件。此阶段由汇编器完成。
gcc -c test.s -o test.o
  • 链接  此阶段完成文件中调用用的各种函数跟静态库和动态库的连接,并将它们一起打包合并形成目标文件,即可执行文件。此阶段由链接器完成。
gcc test.o -o test

        从以上四个阶段来看,我们要使用自己制作的库或者别人的库,一定是汇编完后产生的.o文件,我们只需要对这个.o文件进行链接就可以了;

三、库的制作和使用 

        库是一个二进制文件,想要使用库(给别人使用自己的制作的库或者使用别人的库)一定是由三个部分组成:库文件、头文件、文档说明;一般这个库文件就是函数的定义,头文件就是函数声明,我们只需要将这些打包好,别人使用我们头文件所给的接口就行。

        编写如下四个文件:其中源文件包含add.csub.c,头文件包含add.hsub.h;用来制作静态库并打包

 
//add.h

#pragma once

#include <stdio.h>

extern int my_add(int x, int y);


//add.c

#include "add.h"

int my_add(int x, int y)

{

return x + y;

}


//sub.h

#pragma once

#include <stdio.h>

extern int my_sub(int x, int y);


//sub.c

#include "sub.h"

int my_sub(int x, int y)

{

return x - y;

}

1.静态库的制作  

1.生成二进制(.o)文件

首先将上面的.c文件生成.o文件

2.打包

我们将生成好的.o文件进行打包:

[mlg@VM-20-8-centos test_lib]$ ar -rc libmymath.a add.o sub.o

ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r 选项和-c选项进行打包。

  • -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
  • -c(rreate):建立静态库文件。

我们可以用 ar 命令的 -t 选项和 -v 选项查看静态库当中的文件。

  • -t:列出静态库中的文件。
  • -v:显示详细的信息

3.发布静态库

        静态库要发布出去供别人使用,只要库文件(所有的.o文件)是不够的,我们需要将其和头文件一起发布出去,别人只要看到头文件,就大致了解如何使用了

 当然,我们也可以直接编写Makefile,就不需要我们一步一步的完成静态库的打包及发布

2.静态库的使用

方法一 

        上文中,我们已经有了静态库output了,别人该如何使用呢? 例如:想要在friend文件下使用这个库:

现在在friend目录下有一个mytest.c文件和一个静态库文件lib,mytest.c想要使用lib,我们先编写一下mytest.c代码:

 
  1. #include "add.h"
    
    #include "sub.h"
    
    
    int main()
    
    {
    
    int x = 30;
    
    int y = 20;
    
    
    int ret1 = my_add(x, y);
    
    int ret2 = my_sub(x, y);
    
    
    printf("ret1 = %d\n",ret1);
    
    printf("ret2 = %d\n",ret2);
    
    return 0;
    
    }

 进行编译:

        编译后,报警告:没有头文件,可是明明在lib文件下有我们要的有文件以及库文件,这是为什么呢?

        其实,编译器在编译的时候,会在当前的目录的文件中去找,不会去当前目录的文件夹中去找,lib目录下的头文件以及库文件,和mytest.c不是同级目录,所以编译会出错;

        我们在编译的时候就需要告诉编译器,需要的头文件在哪个目录下。

[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib

        此时,又有警告了:链接错误,未定义的两个函数?在lib目录下已经定义了两个函数,并且打包好了?为什么还是报错呢?

        其原因和上面一样;所以我门还需要告诉编译器库文件在lib目录下:

[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib -L ./lib

头文件和库文件所在位置都告诉编译器了,怎么还是报错呢?

        其实,头文件和库文件都在lib目录下,在mytest.c文件中,是明确的包含了,add.h和sub.h的,gcc在编译的时候能够认识,但不认识库文件,如果在lib目录下有多个库文件,gcc是不知道你想要使用哪个库的。所以我们还需要指明库的名字。

[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib -L ./lib -l mymath

 此时,整个程序才能够正确编译并运行

总结: 

我们在使用静态库进行编译链接时,需要指定头文件的所在路径,库文件的所在路径以及所要掉用的库名称

  • -I:指定头文件所在路径。
  • -L:指定库文件所在路径。
  • -l:指明需要链接库文件路径下的哪一个库
[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib -L ./lib -l mymath

 同样,我们也可以编写Makefile:

方法二

        对比我们之前在编译某个.c文件时,为什么有加上这些选项呢?。这是因为之前的库都是在系统的默认路径下,所以我们可以将我们做好的静态库拷贝到系统的默认路径下,也是可以达到不需要加这些选项的效果;但是严重不推荐。

3.动态库的制作 

        编写如下四个文件:其中源文件包含add.csub.c,头文件包含add.hsub.h;用来制作动态库并打包

 
//add.h

#pragma once

#include <stdio.h>

extern int my_add(int x, int y);


//add.c

#include "add.h"

int my_add(int x, int y)

{

return x + y;

}


//sub.h

#pragma once

#include <stdio.h>

extern int my_sub(int x, int y);


//sub.c

#include "sub.h"

int my_sub(int x, int y)

{

return x - y;

}

1.生成二进制(.o)文件

首先将上面的.c文件生成.o文件 

 
 
  1. [mlg@VM-20-8-centos test_lib]$ gcc -fPIC -c add.c -o add.o

  2. [mlg@VM-20-8-centos test_lib]$ gcc -fPIC -c sub.c -o sub.o

-fPIC:作用是告知编译器 生成位置无关代码(编译产生的代码没有绝对位置,只有相对位置);从而可以在任意地方调用生成的动态库。

2.打包

 我们将生成好的.o文件进行打包:

[mlg@VM-20-8-centos test_lib]$ gcc -shared -o libmymath.so add.o sub.o

-shared:linux在gcc编译时加上 -shared 参数时,目的是使源码编译成动态库 .so 文件;

3.发布动态库

将库文件和所有的头文件组织起来,放到lib目录下,这样就可以发布动态库了

 当然,我们也可以直接编写Makefile,就不需要我们一步一步的完成静态库的打包及发布

4.动态库的使用

        动态库的使用大致和静态库类似,但略有区别。我们先使用静态库的方法来实现动态库的链接。

能够成功编译,但是运行却报错了,为什么呢?

        我们通过ldd命令列出动态库依赖关系,发现是not found。虽然已经告诉了编译器库文件和头文件的路径所在位置,但是当编译器编译好后,就与编译器无关了;当我们执行(运行)可执行程序a.out时,是由加载器来完成的。所以我们需要在运行时,告诉系统库文件在哪里;

方法一

 拷贝到系统的默认路径下,一般指/usr/lib 这里不做演示,严重不推荐;

方法二 

更改  LD_LIBRARY_PATH

[mlg@VM-20-8-centos lib]$ export LD_LIBRARY_PATH=/home/mlg/lesson5-动静态库/friend/lib

        LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径.注意,LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找;

添加好后,我们再次查看,发现路径已经指定好了 

再次编译运行: 

四、动态库与静态库特点总结

静态库的特点:

  • 静态库在可执行程序链接时就加入到可执行代码中,在物理上成为可执行程序的一部分;程序运行时将不再需要该静态库。
  • 相对于动态库链接生成的程序,静态函相当于编译器将代码补充完整了,因此执行程序会大一些,但是运行起来相对快些;
  • 静态库是牺牲了空间效率,换取了时间效率;

动态库的特点:

  • 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在;
  • 动态库只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间;
  • 由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些;
  • 动态库是牺牲了时间效率,换取了空间效率;
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介 《链接器和加载器》讲述构建程序的关键工具——链接器和加载器,内容包括链接和加载、体系结构、目标文件、存储分配、符号管理、库、重定位、加载和覆盖、共享库、动态链接和加载、动态链接的共享库,以及着眼于成熟的现代链接器所做的一些变化;并介绍一个持续的实践项目,即使用Perl语言开发一个可用的小链接器。 《链接器和加载器》适合高校计算机相关专业的学生、实习程序员、语言设计者和开发人员阅读参考。 编辑推荐 《链接器和加载器》:不管你的编程语言是什么,不管你的平台是什么,你很可能总是会涉及链接器和加载器的功能。但是你知道如何最大限度地利用它们吗?只有现在,随着《链接器和加载器》的出版,总算有一本深入完整地彻底揭示编译时和运行时过程的权威著作了。 《链接器和加载器》首先通过实例深入浅出地阐述了在不同的编译器和操作系统中链接和加载过程的差异。在这个基础上,作者提出了清晰实用的忠告,来帮助你创建更快、更清晰的代码。你将会学习如何规避和Windows DLL相关的陷阱,充分利用UNIX ELF库模式等。如果你对程序设计抱有非常认真的态度,那么你可以通过这本书充分地理解这个领域内最难懂的主题之一。《链接器和加载器》对于编译器和操作系统课程同样也是一本理想的补充读物。 《链接器和加载器》特性 ◆覆盖了Windows,UNIX,Linux,BeOS和其它操作系统的动态链接过程。 ◆解释了Java链接模式,以及它是如何应用在网络小应用程序和可扩展Java代码中的。 ◆帮助你编写更优雅、更高效的代码,以及构建能够被更加高效地编译、加裁和运行的应用程序。 ◆包含了一个用Perl构建链接器的练习项目,项目文件可以从网络下载得到。 媒体推荐 “我很享受阅读这本对实现链接器和加载器的众多技术和挑战进行有效概述的书。虽然书中的多数例子都集中在今天被广泛使用的三种计算机体系结构上,但这本书也包含了很多描述过去的一些有趣和古怪的计算机体系结构的注解。通过这些真实的战例,我断定作者本人真正经历了这些事情并存活了下来给我们讲述这个故事。” ——Guy Steele 作者简介 作者:(美国)莱文(John R.Levine) 译者:李勇 莱文(John R.Levine),是很多书籍的作者或合作者,包括Lex & Yacc(O'Reilly),Programming for Graphics Files in C and C++(Wiley),以及7-heIntemetforDummies(IDG)。他还是Journal of C Language Translation的荣誉退休发行人、comp.compilers新闻组的长期仲裁人员,以及某个最早的商用Fortran 77编译器的创建考。他在耶鲁大学获得了计算机科学的博士学位。 目录 第1章 链接和加载 1.1 链接器和加载器做什么? 1.2 地址绑定:从历史的角度 1.3 链接与加载 1.4 编译器驱动 1.5 链接:一个真实的例子 练习 第2章 体系结构的问题 2.1 应用程序二进制接口 2.2 内存地址 2.3 地址构成 2.4 指令格式 2.5 过程调用和寻址能力 2.6 数据和指令引用 2.7 分页和虚拟内存 2.8 Intel 386分段 2.9 嵌入式体系结构 练习 第3章 目标文件 3.1 目标文件中都有什么? 3.2 空目标文件格式:MS-DOS的COM文件 3.3 代码区段:UNIX的a.out文件 3.4 重定位:MS-DOS的EXE文件 3.5 符号和重定位 3.6 可重定位的a.out格式 3.7 UNIX的ELF格式 3.8 IBM 360目标格式 3.9 微软可移植、可执行体格式 3.10 Intel/Microsoft的OMF文件格式 3.11 不同目标格式的比较 练习 项目 第4章 存储空间分配 4.1 段和地址 4.2 简单的存储布局 4.3 多种段类型 4.4 段与页面的对齐 4.5 公共块和其他特殊段 4.6 链接器控制脚本 4.7 实际中的存储分配 练习 项目 第5章 符号管理 5.1 绑定和名字解析 5.2 符号表格式 5.3 名称修改 5.4 弱外部符号和其他类型符号 5.5 维护调试信息 练习 项目 第6章 库 6.1 库的目的 6.2 库的格式 6.3 建立库文件 6.4 搜索库文件 6.5 性能问题 6.6 弱外部符号 练习 项目 第7章 重定位 7.1 硬件和软件重定位 7.2 链接时重定位和加载时重定位 7.3 符号和段重定位 7.4 基本的重定位技术 7.5 可重链接和重定位的输出格式 7.6 其他重定位格式 7.7 特殊情况的重定位 练习 项目 第8章 加载和覆盖 8.1 基本加载 8.2 带重定位的基本加载 8.3 位置无关代码 8.4 自举加载 8.5 树状结构的覆盖 练习 项目 第9章 共享库 9.1 绑定时间 9.2 实际的共享库 9.3 地址空间管理 9.4 共享库的结构 9.5 创建共享库 9.6 使用共享库链接 9.7 使用共享库运行 9.8 malloc hack和其他共享库问题 练习 项目 第10章 动态链接和加载 10.1 ELF动态链接 10.2 ELF文件内容 10.3 加载一个动态链接程序 10.4 使用PLT的惰性过程链接 10.5 动态链接的其他特性 10.6 运行时的动态链接 10.7 微软动态链接库 10.8 OSF/1伪静态共享库 10.9 让共享库快一些 10.10 几种动态链接方法的比较 练习 项目 第11章 高级技术 11.1 C++的技术 11.2 增量链接和重新链接 11.3 链接时的垃圾收集 11.4 链接时优化 11.5 链接时代码生成 11.6 Java链接模型 练习 项目 参考文献 序言 几乎从有计算机以来,链接器和加栽器就是软件开发工具包中的一部分,因为它们允许使用模块(而不是一个单独的大文件)来构建程序的关键工具。 早在1947年,程序员们就开始使用原始的加载器:将程序的例程存储在多个不同的磁带上,并将它们合并、重定位为一个程序。在20世纪60年代早期,这些加栽器就已经发展得相当完善了。由于那时内存很贵且容量有限,计算机的速度很慢(以今天的标准),为了创建复杂的内存覆盖策略(以将大容量的程序加载到小容量内存中),以及重新编辑先前链接过的文件(以节省重新创建程序的时间),这些链接器都包含了很多复杂的特性。 20世纪七八十年代,链接技术几乎没有什么进展。链接器趋向于更加简单,虚拟内存技术将应用程序和覆盖机制中的大多数存储管理工作都转移给了操作系统,越来越快的计算机和越来越大的磁盘也使得重新链接一个程序或替换个别模块比仅仅链接改变过的地方更加容易了。从20世纪90年代起,链接器又开始变得复杂起来,增加了诸多现代特性,包括对动态链接共享库的支持和对C++独特要求的支持。同时,像IA64那样具有宽指令字和编译时访存调度特性的先进处理器架构,也需要将一些新的特性加入到链接器中,以确保在被链接的程序中可以满足代码的这些复杂需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值