Linux下的静态链接库和动态链接库

零、链接库概述

Linux下得库有动态与静态两种,动态通常用.so为后缀,静态用.a为后缀。面对比一下两者:

  • 静态链接库:当要使用时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。
  • 动态库:某个程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码(Position Independent Code (PIC))。

动态链接库的加载方式有两种:隐式加载和显示加载。

linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。

一、静态链接库

1.1 编辑测试文件

两个文件:add.c、 sub.c、add.h 、sub.h 和 main.c

/*add.h */
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
#endif/*add.c*/
#include "add.h"
int add(int a, int b)
{return a+b;
}/*sub.h*/
#ifndef _SUB_H_
#define _SUB_H_
int sub(int a, int b);
#endif/*sub.c*/
#include "add.h"
int sub(int a, int b)
{return a-b;
}/*main.c*/
#include <stdio.h>
#include "add.h"
#include "sub.h"
int main(void)
{printf("1 + 2 =%d\n", add(1, 2));printf("1 - 2 =%d\n", sub(1, 2));return 0;
}

1.2 将.c 编译生成 .o文件

gcc -c add.c
gcc -c sub.c

生成的文件:sub.o ,add.o

无论是静态库文件还是动态库文件,都是由 .o 文件创建的。

1.3 由 .o 文件创建.a静态库

ar cr libmymath.a sub.o add.o
  • ar:静态函数库创建的命令

    -c :create的意思

    -r :replace的意思,表示当前插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误信息,并不替换其他同名的模块。默认的情况下,新的成员增加在库的结尾处。

库文件的命名规范是以lib开头(前缀),紧接着是静态库名,以 .a 为后缀名。

1.4 在程序中使用静态库

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

-L 指定函数库查找的位置,注意L后面还有.,表示在当前目录下查找

-l 指定函数库名,其中的lib和.a(.so)省略。

区分:-L是指定查找位置,-l指定需要操作的库名。

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名(是mymath 而不是libmymath.a),gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。在程序main.c中,我们包含了静态库的头文件add.h和sub.h,然后在主程序main中直接调用公用函数add()sub()即可。

1.5 生成目标程序main,然后运行。

./main
1 + 2 = 3
1 - 2 = -1

二、动态库(隐式链接)

2.1 由 .o创建.so动态库

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为mymath,则动态库文件名就是libmymath.so。用gcc来创建动态库。在系统提示符下键入以下命令得到动态库文件libmymath.so。

gcc -fPIC -o add.o -c add.c
gcc -fPIC -o sub.o -c sub.c
gcc -shared -o libmymath.so add.o sub.o

或者:

gcc –c –o add.o add.c
gcc –c –o sub.o sub.c
gcc -shared -fPCI -o libmyhello.so add.o sub.o

其中:

-fpic:产生代码位置无关代码

-shared :生成共享库

2.2 隐式方式使用动态库

在程序中隐式使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。

gcc -o main main.c -L. -lmymath
./main
./main: error while loading shared libraries:libmymath.so: cannot open shared object file: No such file or directory

此时会出错了,查看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。

  • 动态库的搜索路径搜索的先后顺序是:
    1.编译目标代码时指定的动态库搜索路径;
    2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
    3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;

    只需在在该文件中追加一行库所在的完整路径如"/root/test/conf/lib"即可,然后ldconfig是修改生效。

    4.默认的动态库搜索路径/lib
    5.默认的动态库搜索路径/usr/lib

  • 为此解决方法:
    1.我们将文件libmyhello.so复制到目录/usr/lib中:
    2.将libmyhello.so拷贝到可执行文件main的同一目录下。

再次运行:

./main
1 + 2 = 3
1 - 2 = -1

成功了!这也进一步说明了动态库在程序运行时是需要的。

2.3 动态库的初始化和解析

Windows下的动态库加载,卸载都会有初始化函数以及卸载函数来完成库的初始化以及资源回收,linux当然也可以实现,这些初始化函数主要包含两个部分:动态库的构造和析构函数机制、动态库的全局变量初始化工作。

(1)动态库的构造和析构函数机制

在Linux中,提供了一个机制:在加载和卸载动态库时,可以编写一些函数,处理一些相应的事物,我们称这些函数为动态库的构造和析构函数,其代码格式如下:

void __attribute__ ((constructor)) my_init(void);  // my_init为自定义的构造函数名
void __attribute__ ((destructor)) my_fini(void);  //my_fini为自定义的析构函数名

在编译共享库时,不能使用"-nonstartfiles"或"-nostdlib"选项,否则构建与析构函数将不能正常执行(除非你采取一定措施)。

注意,构造函数的参数必须为空,返回值也必须为空。

举个例子,动态库文件a.c的代码如下:

void __attribute__((constructor)) my_init(void)
{printf("init library\n");
}

编译成动态库:

gcc -fPIC -shared a.c -o liba.so

主程序main.c如下:

#include<stdlib.h>
#include<stdio.h>
int main()
{pause();return 0;
}

编译:

gcc -L./ -la main.c -o main.bin

运行main.bin程序:

也就是说,在运行main时,加载完liba.so后,自动运行liba.so的初始化函数。

(2)全局变量初始化

① 先看如下例子:

//文件名:b1.c
#include<stdlib.h>
#include<stdio.h>int reti()
{printf("reti\n");return 10;
}int g1=reti();  // g1是个全局变量。

使用GCC对其进行编译:

gcc -fPIC -shared b1.c -o libb.so

编译错误!使用G++对其进行编译:

g++ -fPIC -shared b1.c -o libb.so

编译成功!可见GCC和G++对于这种全局变量初始化的方法,支持力度是不一样的。

//主程序:main.c
#include <stdlib.h>
#include <stdio.h>
int main()
{pause();return 0;
}

编译执行文件:

gcc -L./ -lb main.c -o main.bin

运行main.bin:

这说明,进程在加载libb.so后,为了初始化全局变量g1,其会运行reti来初始化g1。

② 再来看一个C++的例子:

//文件名:b2.cppclass Myclass
{public:Myclass();int i;
};Myclass::Myclass(){printf("constructMyclass\n");
};Myclass g1;

编译动态库:

g++ -fPIC -shared b2.cpp-o libb.so

在动态库libb.so中,声明了一个类型为Myclass的全局变量g1。

//主程序:main.cpp
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>int main()
{pause();return 0;
}

编译执行文件:

g++ -L./ -lb main.cpp -o main.bin

运行main.bin:

这说明,进程在加载liba.so后,为了初始化全局变量g1,程序在进入main函数前将会运行Myclass的构造函数。

三、动态链接库(显式链接)

3.1 重要的dlfcn.h头文件

LINUX下使用动态链接库,源程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型。下面详细说明一下这些函数。

函数 dlerror:

原型为: const char *dlerror(void);

当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。

**函数 dlopen:**打开指定的动态链接库文件

原型为: void *dlopen (const char *filename, int flag);

dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。

filename: 如果名字不以/开头,则非绝对路径名,将按下列先后顺序查找该文件:

  1. 用户环境变量中的LD_LIBRARY值;

  2. 动态链接缓冲文件/etc/ld.so.cache

  3. 目录/lib,/usr/lib

flag表示在什么时候解决未定义的符号(调用)。取值有两个:

  1. RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。
  2. RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。

dlopen调用失败时,将返回NULL值,否则返回的是操作句柄。

函数 dlsym : 取函数执行地址

原型为: void *dlsym(void *handle, char *symbol);

dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。

如程序代码:

void (*add)(int x,int y);  /* 说明一下要调用的动态函数add */
add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */
add(89,369);               /* 带两个参数89和369调用add函数 */

函数 dlclose : 关闭动态链接库

原型为: int dlclose (void *handle);

dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

3.2 显加载示动态链接库的实例

在下面这个实例中将通过动态加载libmymath.so链接库,来调用add()和sub()两个函数。

/*main.c*/
#include <stdio.h>
#include <dlfcn.h>int main(void)
{void*dp=dlopen("libmymath.so",RTLD_LAZY);if(NULL==dp){printf("打开动态链接库时失败!");return1;}//定义函数指针int(*fn_add)(int,int)=NULL;int(*fn_sub)(int,int)=NULL;fn_add=dlsym(dp,"add");fn_sub=dlsym(dp,"sub");if(NULL==fn_add|| NULL==fn_sub){printf("在动态链接库中寻找函数失败!");return1;}printf("1+ 2 = %d\n", fn_add(1, 2));printf("1- 2 = %d\n", fn_sub(1, 2));dlclose(dp);return 0;
}

将libmymath.so和main.c放在同一个目录下,执行如下命令:

gcc -rdynamic -s -o main.bin main.c -ldl

-rdynamic 选项以指定输出文件为动态链接的方式

-s 指定删除目标文件中的符号表,

-ldl 则指示装配程序ld需要装载dl函数库。

最后运行main.bin的结果同上。

四、Windows下和Linux下显示加载动态链接库的比较

Windows下动态链接库以“.dll”为后缀,而Linux下得动态链接库是以”.so”为后缀的。

函数功能 Windows下 Linux下
加载动态链接库 LoadLibrary dlopen
获取动态链接库中的函数地址 GetProcAddress dlsym
关闭动态链接库 FreeLibrary dlclose
使用时包含的头文件 Winbase.h(include Windows.h) dlfcn.h

五、特殊情况

我们回过头看看,发现使用静态库和隐式方式使用动态库时编译成目标程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?抱着对问题必究到底的心情,来试试看。先删除除.c和.h外的所有文件,恢复成我们刚刚编辑完举例程序状态。

gcc -c add.c
gcc -c sub.c
ar cr libmymath.a sub.o add.o
gcc -shared -fPCI -o libmyhello.so sub.o add.o

现在目录有两个同名的库文件(动态库文件和静态库文件同名):

libmymath.a 、 libmymath.so

编译运行程序:

gcc -o main main.c -L. -lmymath
./main
./main: error while loading shared libraries:libmymath.so: cannot open shared object file: No such file or directory

从程序./main运行的结果中很容易知道,当Linux静态库和Linux动态库同名时, gcc命令将优先使用动态库。如果强制使用静态库则需要加 -static 选项支持,即:

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

链接静态库的可执行程序明显比链接动态库的可执行文件大。

六、查看库中的符号

使用nm命令可以打印出库中涉及到的所有符号。库既可以是静态库也可以是动态的。

常见的三种符号:

  1. 在库中被调用,但没有在库中定义(表明需要其他库支持),用 U 表示

  2. 在库中定义的函数,用 T 表示

  3. “弱态”符号,他们虽然在库中被定义但是可能被其他库中同名的符号覆盖,用 W 表示。

用ldd命令可以查看一个可执行程序依赖的共享库。

七、Linux下so导出指定函数

Linux下编译so导出源文件里面的指定函数:

  1. 在文件里面最前面加上:#defineDLL_PUBLIC attribute((visibility("default")))

  2. 在文件里面需要导出的函数前加上:extern "C" DLL_PUBLIC

  3. Linux下动态库(so)编译时默认不导出,在Makefile中需要添加:-fvisibility=hidden

Linux下的静态链接库和动态链接库相关推荐

  1. Linux下的静态链接库和动态链接库编程

    Linux下的静态链接库和动态链接库编程 参考: Linux下的静态链接库和动态链接库编程 - Histring - 博客园 Mac下静态库和动态库的创建和使用_C/C++_vincent2610的专 ...

  2. cnsl是什么意思_VS2010下创建静态链接库和动态链接库

    VS2010下创建静态链接库和动态链接库 类封装成dll如果你的工作长期与某个领域相关,比如说长期做直接体绘制 (DVR)方面的开发,那么你可能经常使用自己的传递函数类,如果每一个工程你都把传递函数类 ...

  3. linux下静态链接库和动态链接库

    关于链接库的知识,网上太多资料了,但是并不代表我很熟悉.今天遇到了 一个问题,就是由于静态链接库和ubuntu系统不兼容导致的,虽然花了点时间才搞定 但是,其中暴露的问题也不少. 没有区分好静态链接库 ...

  4. 静态链接库与动态链接库 (二)动态链接库的编译与使用

    上一篇文章里大概描述linux下静态链接库的编译与使用,下面讲动态链接库的编译与使用方法. 1. 什么是动态链接库 所谓动态链接库,是指编译的时候不会把程序引用到的库插入到执行程序里,而是在执行时候才 ...

  5. 静态链接库、动态链接库和动态加载库

    前言 静态库是obj文件的一个集合(目标文件中通常仅解析了文件内部的变量和函数,对于引用的函数和变量还没有解析,这需要将其他已经编写好的目标文件引用进来,将没有解析的函数和变量进行解析,通常引用的目标 ...

  6. UNIX环境高级编程(三)—— 静态链接库与动态链接库

    动态链接库一般不开放源码,需要建工程才能使用(其中提供的接口和 API): 0. .a/.o/.so What are .a and .so files? .o 就相当于 windows 里的 obj ...

  7. 35.静态链接库和动态链接库

    35.1.函数库的前世今生 (1)函数库就是一些事先写好的函数的集合,因为函数是模块化的,因此可以被复用:我们写好了某个函数,可以被反复使用,譬如A写好了某个函数然后共享出来,当B有相同的需求时就不需 ...

  8. 【四、静态库与动态库(共享库)】揭开链接库的神秘面纱:手把手教你制作静态链接库与动态链接库

    前言 不管是在 Windows 下开发,还是在 Linux 下开发,我们都会经常性的使用一些库文件,这些库文件的特点就是,我们可以看到接口的原型并通过这些接口来调用这个函数的功能,但是我们无法查看这个 ...

  9. 静态链接库与动态链接库详解

    以下内容源于C语言中文网相关内容的学习整理,如有侵权请告知删除. 一.库文件的简介 库文件,其等价为压缩包文件.该文件内部通常包含不止一个目标文件(也就是二进制文件),每个目标文件存储的代码,并非完整 ...

最新文章

  1. 《C++面向对象高效编程(第2版)》——2.16 识别成员函数的目标对象
  2. UA MATH571A QE练习 R语言 单因子试验的回归分析
  3. CG CTF WEB file_get_contents
  4. C:02---scanf、printf
  5. 网络编程-关于get请求与post请求
  6. 《淘宝网开店 进货 运营 管理 客服 实战200招》——1.9 熟悉网上开店的流程...
  7. Hadoop生态系统完整组件及其在架构中的作用
  8. 《PHASEN:A Phase and Harmonics-Aware Speech Enhancement Network》Pytorch代码学习
  9. truetype字体怎么转换成普通字体_TrueType字体该如何设置呢?
  10. 学 Python 都用来干嘛的?
  11. 初步学习MOOS-ivp
  12. EARTHDATA网站(modis数据下载流程),超级详细,附有火狐浏览器downthemall使用流程,以及默认路径修改方式
  13. 查找文件及文件内容查找
  14. Android IPC机制之IPC概念、Android 多进程和相关基础知识
  15. pandorabox开启ssr_华硕及PandoraBox固件如何启用SSH和SFTP连接
  16. Task02 几何变换
  17. 前生殖器期和生殖期的差别?来自弗洛伊德的心理性欲发展阶段理论|小白心理-312/347考研答疑
  18. html,css常见的几种垂直居中方式
  19. Nature Communication 投稿要求
  20. 4、OpenGL入门 贴图【Win32+VS2019】亲测代码可用

热门文章

  1. 最简单的爬虫案例开发,Python原生爬虫
  2. AAAI 2023 | LipCDE: 连续时间观测下的因果效应推断
  3. 微信小程序云函数,以获取openid为例
  4. 2020.01-Study_update.1
  5. Opencv加QOpenGLWidget 手撸视频播放器,实现自己的看片神器
  6. GBase 8a MPP v9版本新增的VC功能是什么?
  7. LiveGBS国标视频平台接入4G移动执法仪如何保存设备的执法录像实时录像和云端录像
  8. linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)
  9. TypeScript入门指南:从JS到TS的转变
  10. python种颜色循环_python图像主色调(主颜色)提取,在hsv空间k均值迭代实现