热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

_如何生成linux下的动态库和静态库?一篇文章带你读懂“库”

一、什么是库?在windows平台和linux平台下都大量存在着库。一般是软件作者为了发布方便、替换方便或二次开发目的,而发布的一组可以单独与应用程序进行compil

一、什么是库?

在windows平台和linux平台下都大量存在着库。一般是软件作者为了发布方便、替换方便或二次开发目的,而发布的一组可以单独与应用程序进行compile time或runtime链接的二进制可重定位目标码文件。

本质上来说库是一种可执行代码的二进制形式,这个文件可以在编译时由编译器直接链接到可执行程序中,也可以在运行时由操作系统的runtime enviroment根据需要动态加载到内存中。

一组库,就形成了一个发布包,当然,具体发布多少个库,完全由库提供商自己决定。

由于windows和linux的本质不同,因此二者库的二进制是不兼容的。

现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

本文仅讨论linux下的库。

二、库的分类

库有两种:静态库和共享库(动态库)。

win32平台下,静态库通常后缀为.lib,动态库为.dll ;

linux平台下,静态库通常后缀为.a,动态库为.so 。

从本质上来说,由同一段程序编译出来的静态库和动态库,在功能上是没有区别的。不同之处仅仅在于其名字上,也就是“静态”和“动态”。

二者均以文件的形式存在,其本质上是一种可执行代码的二进制格式,可以被载入内存中执行。无论是动态链接库还是静态链接库,它们无非是向其调用者提供变量、函数和类。

1. 静态库

所谓静态库,就是在静态编译时由编译器到指定目录寻找并且进行链接,一旦链接完成,最终的可执行程序中就包含了该库文件中的所有有用信息,包括代码段、数据段等。

2. 动态库

所谓动态库,就是在应用程序运行时,由操作系统根据应用程序的请求,动态到指定目录下寻找并装载入内存中,同时需要进行地址重定向。

3. 区别

我们以编译链接、载入时刻两点来讨论静态库和动态库的区别。

编译链接

静态链接库在程序编译时会被链接到目标代码中,目标程序运行时将不再需要改动态库,移植方便,体积较大,浪费控件和资源,因为所有相关的对象文件与牵涉到库都被链接合成一个可执行文件,这样导致可执行文件的体积较大。

动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因为可执行文件体积较小。有了动态库,程序的升级会相对比较简单,比如某个动态库升级了,只需要更换这个动态库的文件,而不需要去更换可执行文件。但要注意的是,可执行程序在运行时需要能找到动态库文件。可执行文件时动态库的调用者。

载入时刻

二者的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。

4. 优缺点

相对于动态库,静态库的优点在于直接被链接进可执行程序中,之后,该可执行程序就不再依赖于运行环境的设置了(当然仍然会依赖于 CPU指令集和操作系统支持的可执行文件格式等硬性限制)。

而动态库的优点在于,用户甚至可以在程序运行时随时替换该动态库,这就构成了动态插件系统的基础。具体使用静态库和动态库,由程序员根据需要自己决定。

三、库文件的制作

1. 库文件命名

静态库的名字一般为,其中xxxx是该lib的名称;动态库的名字一般为,含义如下图所示:

2. 制作库文件常用参数

首先需要了解gcc编译库要用到一些参数,很重要。

3. 库源文件

假定我们要将以下两个文件制作成库文件

int add(int x,int y)
{
return x+y;
}
int sub(int x,int y)
{
return x-y;
}
int add(int x,int y);
int sub(int x,int y);

4. 制作静态库并使用

(1)需要把 编译成.o文件

gcc -c

(2)使用 ar 命令生成静态库

ar -rc add.o 遵循静态库命名的规则 lib + 名字 + .a

(3)使用静态库 要是用静态库,只需要包含,就可以使用函数add()、sub()。

#include
#include""
void main()
{
printf("add(5,4) is %d\n",add(5,4));
printf("sub(5,4) is %d\n",sub(5,4));
}

静态库的文件可以放在任意的位置,编译时只需要找到该库文件即可。

gcc -o run

(4)库和头文件如果在其他目录下

使用以下命令编译:

gcc -c -I /home/xxxx/include //假设要使用对应的静态库
gcc -o test -L /home/xxxxx/lib test.o

或者

gcc -c -I /home/xxxx/include -L /home/xxxxx/lib

1). 通过-I(是大i)指定对应的头文件

2). 通过-L制定库文件的路径,就是要用的静态库。

3). 在中要包含静态库的头文件。

5. 制作动态库并使用

(1)把编译成动态链接库

gcc -fPIC -o libadd.o -c
gcc -shared -o libadd.o

也可以直接使用一条命令

gcc -fPIC -shared -o

(2)动态库的安装 通常动态库拷贝到/lib下即可:

sudo cp /lib

(3)使用动态库

#include
#include""
void main()
{
printf("add(5,4) is %d\n",add(5,4));
printf("sub(5,4) is %d\n",sub(5,4));
}

编译动态库:

gcc static -o run -ladd

注意观察编译时动态库的名字与库文件对应关系

去掉 .so, lib简化成l,其他字母保留。

6. 动态加载的函数库Dynamically Loaded (DL) Libraries

动态加载的函数库Dynamically loaded (DL) libraries是一类函数库,它可以在程序运行过程中的任何时间加载。它们特别适合在函数中加载一些模块和plugin扩展模块的场合,因为它可以在当程序需要某个plugin模块时才动态的加载。

Linux系统下,DL函数库与其他函数库在格式上没有特殊的区别,它们创建的时候是标准的object格式。主要的区别就是这些函数库不是在程序链接的时候或者启动的时候加载,而是通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。通常C语言环境下,需要包含这个头文件。

dlopen()

dlopen函数打开一个函数库然后为后面的使用做准备。C语言原型是:

如果有好几个函数库,它们之间有一些依赖关系的话,例如X依赖Y,那么你就要先加载那些被依赖的函数。例如先加载Y,然后加载X。

dlerror()

通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。

dlsym()

如果你加载了一个DL函数库而不去使用当然是不可能的了,使用一个DL函数库的最主要的一个函数就是dlsym(),这个函数在一个已经打开的函数库里面查找给定的符号。这个函数如下定义:

void * dlsym(void *handle, char *symbol);

dlclose()

dlopen()函数的反过程就是dlclose()函数,dlclose()函数用力关闭一个DL函数库。Dl函数库维持一个资源利用的计数器,当调用dlclose的时候,就把这个计数器的计数减一,如果计数器为0,则真正的释放掉。真正释放的时候,如果函数库里面有_fini()这个函数,则自动调用_fini()这个函数,做一些必要的处理。Dlclose()返回0表示成功,其他非0值表示错误。

举例

#include
#include
void main()
{
int (*add)(int x,int y);
int (*sub)(int x,int y);
void *libptr;
libptr=dlopen("./",RTLD_LAZY); //加载动态库
add=dlsym(libptr,"add"); //获取函数地址
sub=dlsym(libptr,"sub");
printf("add(5,4) is %d\n",add(5,4));
printf("sub(5,4) is %d\n",sub(5,4));
dlclose(libptr);
}

四、库的两个查看命令

1.查看依赖库命令ldd

使用ldd命令可以查看一个可执行程序依赖哪些库。

这个命令非常有用,实际工作中经常会一直各种库,而有些程序的执行需要依赖好几种库,各种库的版本有很多历史版本,经常会出现库不兼容的情况,我们需要根据实际情况,适当的降低版本或者升级版本。

例如:

可以看到线程库依赖于libc库和ld-linux库。

2.nm

nm工具可以打印出库中的涉及到的所有符号,下面是我们查看我们创建的动态库:

五、库的安装

在新安装一个库之后如何让系统能够找到他,有以下几种方法:

1. 拷贝到/lib或者/usr/lib下

如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。如果安装在其他目录,需要将其添加到/etc/文件中,步骤如下

2.通过配置文件/etc/profile

永久生效的环境变量设置,编辑/etc/profile即可。

vi /etc/profile

在文件里末尾加上对应的环境变量信息。

动态库环境变量设置:

export LD_LIBRARY_PATH=/home/peng/mylib/

/home/peng/mylib/指的是动态库文件夹所在位置。即,.so等文件在/home/peng/mylib/下。

编辑完成,保存编辑并退出; 使配置即时生效:

source /etc/profile

3./etc/

编辑/etc/文件,加入库文件所在目录的路径

vim /etc/

在里面添加动态库所在路径即可,例如

/usr/local/lib/

运行ldconfig,该命令会重建/etc/文件

七、常见库的移植

jpeg库,用于jpeg图像处理

下载地址:

ijg.org/files/

解压

tar xvzf jpegsrc.v6b.tar.gz
cd jpeg-6b

生成Makefile

./configure --host=arm-linux-gnueabihf --prefix=$PWD/temp_install

编译, 安装

make
make install

注意这个库的安装程序有BUG,不会自动创建发布的lib,include,man等,因此要手工创建,要不先把其它库做好,再安装这个库

mkdir -p /home/peng/jpeg-6b/temp_install/include
mkdir -p /home/peng/jpeg-6b/temp_install/lib
mkdir -p /home/peng/jpeg-6b/temp_install/man/man1

看到这里你是不是对“Linux”又有了一点新的认知呢~

如果你喜欢这篇文章的话,动动小指,加个关注哦~

如果你也想成为程序员,想要快速掌握编程,这里为你分享一个学习企鹅圈子!

里面有资深专业软件开发工程师,在线解答你的所有疑惑~编程语言入门“so easy”

资料包含:编程入门、游戏编程、课程设计、黑客等。




推荐阅读
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • Linux环境下进程间通信:深入解析信号机制
    本文详细探讨了Linux系统中信号的生命周期,从信号生成到处理函数执行完毕的全过程,并介绍了信号编程中的注意事项和常见应用实例。通过分析信号在进程中的注册、注销及处理过程,帮助读者理解如何高效利用信号进行进程间通信。 ... [详细]
  • 主调|大侠_重温C++ ... [详细]
  • 本文详细介绍了C语言中的基本数据类型,包括整型、浮点型、字符型及其各自的子类型,并探讨了这些类型在不同编译环境下的表现。 ... [详细]
  • 本文探讨了如何通过预处理器开关选择不同的类实现,并解决在特定情况下遇到的链接器错误。 ... [详细]
  • Linux环境下C语言实现定时向文件写入当前时间
    本文介绍如何在Linux系统中使用C语言编程,实现在每秒钟向指定文件中写入当前时间戳。通过此示例,读者可以了解基本的文件操作、时间处理以及循环控制。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 本文探讨了C++编程中理解代码执行期间复杂度的挑战,特别是编译器在程序运行时生成额外指令以确保对象构造、内存管理、类型转换及临时对象创建的安全性。 ... [详细]
  • 本文详细介绍了get和set方法的作用及其在编程中的实现方式,同时探讨了点语法的使用场景。通过具体示例,解释了属性声明与合成存取方法的概念,并补充了相关操作的最佳实践。 ... [详细]
  • 本文详细介绍了如何在PHP中进行数组删除、清空等操作,并提供了在Visual Studio Code中创建PHP文件的步骤。 ... [详细]
  • 本文深入探讨了面向切面编程(AOP)的概念及其在Spring框架中的应用。通过详细解释AOP的核心术语和实现机制,帮助读者理解如何利用AOP提高代码的可维护性和开发效率。 ... [详细]
  • 本文深入探讨了 Java 中 LocalTime 类的 isSupported() 方法,包括其功能、语法和使用示例。通过具体的代码片段,帮助读者理解如何检查特定的时间字段或单位是否被 LocalTime 类支持。 ... [详细]
author-avatar
气质女__人爱美必修课
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有