动态库学习[通俗易懂]

动态库学习[通俗易懂]总结一:动态库前言 我们知道程序编译链接经常使用动态,同时我们可能还知道,动态库时程序运行时加载的。但是动态库到底有什么作用,如何生成、如何加载等,我们却很少关注。接下来,我给大家做一个简单的介绍。1.1动态库和静态库的区别静态库特点(linux):命名上是以*.o结尾静态库在链接阶段直接就加入到可执行的文件中了,在执行过程中无需该静态库相对于动态库生成的文件,使用静态库生…


Jetbrains全系列IDE稳定放心使用


总结一:动态库

前言

 
我们知道程序编译链接经常使用动态,同时我们可能还知道,动态库时程序运行时加载的。但是动态库到底有什么作用,如何生成、如何加载等,我们却很少关注。接下来,我给大家做一个简单的介绍。

1.1 动态库和静态库的区别

静态库特点(linux):

  • 命名上是以 *.o 结尾
  • 静态库在链接阶段直接就加入到可执行的文件中了,在执行过程中无需该静态库
  • 相对于动态库生成的文件,使用静态库生成的文件连接生成的可执行文件较大

动态库的特点(linux)

  • 命名上是以 *.so
  • 目标文件在链接阶段只是指明链接的那个动态库,动态库与目标文件保持独立。在执行过程中需要该动态库
  • 使用动态库生成的目标文件较小

对于工程中比较共通的源码文件,比如多个进程使用同一个模块的源码,我们最好将其制作成动态库,以节省系统空间。同时如果动态库出现bug,只需要重新生成一个动态库并将以前的替换即可。不需要重新编译其他模块。

1.2 内存中的动态库

在讲到动态库的装载时我们需要懂一定的背景知识,首先虚拟内存和物理内存,其次还有地址映射,这些知识就不在本文多加讲解,网上资料很多。我们动态库在整个内存空间是有一份,而每个进程都有自己的虚拟空间,虚拟空间会使用匿名映射(mmap使用MAP_PRIVATE方式进行映射),使自己的进程与动态库进行关联。本进程只会保留访问动态库时的一些数据。好了,打的方向就说这么多,这几句话随便抽出一个词来都够将好久。我们只需要对大方向有认识即可。以下就是映射表

在这里插入图片描述

此外我们说一些额外的小知识,在linux系统中 /proc 目录下有很多进程文件。在执行的进程都会创建一个文件。随便进入一个文件 /etc/1892。查看maps文件(sudo cat maps),这里面对应了动态库对应虚拟空间的位置,值得注意的是,这个文件最后一列有多少个[stack]就有多少个线程

00008000-005ba000 r-xp 00000000 b3:01 11822      /usr/local/bin/ecTelematicsApp
005c1000-005df000 rw-p 005b1000 b3:01 11822      /usr/local/bin/ecTelematicsApp
005df000-00d7f000 rw-p 00000000 00:00 0          [heap]
a8c00000-a8cff000 rw-p 00000000 00:00 0 
a8cff000-a8d00000 ---p 00000000 00:00 0 
a8e00000-a8e01000 ---p 00000000 00:00 0 
a8e01000-a9600000 rw-p 00000000 00:00 0          [stack:1369]
a9600000-a9700000 rw-p 00000000 00:00 0 
a9700000-a9721000 rw-p 00000000 00:00 0 
a9721000-a9800000 ---p 00000000 00:00 0 
.......
b5c5d000-b645e000 rw-p 00000000 00:00 0          [stack:1961]
b645e000-b6470000 r-xp 00000000 b3:01 709        /lib/libresolv-2.19.so
b6470000-b6477000 ---p 00012000 b3:01 709        /lib/libresolv-2.19.so
b6477000-b6478000 r--p 00011000 b3:01 709        /lib/libresolv-2.19.so
b6478000-b6479000 rw-p 00012000 b3:01 709        /lib/libresolv-2.19.so
b6479000-b647b000 rw-p 00000000 00:00 0 
.......
bea5d000-bea7e000 rw-p 00000000 00:00 0          [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

同时我们也可以直接使用readelf -m [bin文件名],来读取该bin文件的链接信息。

1.3 动态库的加载

关于动态库我当初的直接认识是,程序运行到调用该动态库的接口时,会产生缺页,从而去磁盘加载动态库到内存,然后再执行。但事实并非如此。动态库也分为隐式链接和显示链接,不同的方式其载入内存的时间也是大相径庭。

显示链接 隐式链接
语法 不需要申明动态库先关的头文件,在调用时需要加载动态库的名称 只需要添加相应的头文件即可
加载 执行到相应代码段时加载动态库(可以控制库的加载和卸载) 由系统控制加载时间,一般在程序启动时就加载

由以上两点我们可以看出显示链接如果控制得当,对内存的消耗将下降许多,大型项目应该使用显示链接。但是缺点也有,就是如果库不存在,隐式链接可以再一开始就发现库不存在,而显示链接会被偶然触发。

1.4 动态库的制作

首先我们准备一个源文件 print.c

#include<stdio.h>

void printInter()
{
    printf("%s\n", __FUNCTION__);
}

void printExtern()
{
    printInter();
    printf("%s\n", __FUNCTION__);
}

输入指令

gcc -fPIC -c print.c -o print.o
gcc -shared print.o -o libprint.so -lstdc++

由此我们生成了 libprint.so动态库。

然后我们再创建libcurl.so 的接口头文件print.h

#ifndef __PRINTT_H_
#define __PRINT_H__

void printExtern();

#endif

1.5 动态库的隐式链接

我们创建main.c 去使用库

#include<stdio.h>
#include<unistd.h>
#include "print.h"

int main()
{
	printf("waite 5 seconds\n");
	sleep(5);
	printf("%s\n", __FUNCTION__);
	printExtern();

	return 0;
}

输入指令

gcc -fPIC -c main.c -o main.o
gcc -o target main.c -L./ -lprint -I./ -lstdc++

生成target

执行 ./target, 输出如下

waite 5 seconds
main
printInter
printExtern

我们可以通过 readelf -d target,可以查看target链接了libprint.so

root@user:/home/cjj/jianxiongs/so2# readelf -d target 

Dynamic section at offset 0xf0c contains 25 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libprint.so]
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x80483fc
 0x0000000d (FINI)                       0x8048604
 0x00000019 (INIT_ARRAY)                 0x8049f00
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x8049f04
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x80481ac
 0x00000005 (STRTAB)                     0x80482c8
 0x00000006 (SYMTAB)                     0x80481e8
 0x0000000a (STRSZ)                      208 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x804a000
 0x00000002 (PLTRELSZ)                   32 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x80483dc
 0x00000011 (REL)                        0x80483d4
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x80483b4
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x8048398
 0x00000000 (NULL)                       0x0

接下来我们删除 libprint.so。 然后再运行target,这时我们发现出现如下错误

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

这说明程序在一开始就要加载libprint.so的库,这就是动态库的隐式链接

1.6 动态库的显式链接

这时我们需要更改main.c的内容,修改最终如下

#include<stdio.h>
#include<unistd.h>
#include<dlfcn.h>
#include<stdlib.h>
//#include "print.h"  删除引用头文件

void test()
{
    char *libname = NULL;
    char *err = NULL;

    //open the lib
    void *handle = dlopen("./libprint.so", RTLD_NOW);
    if(!handle)
    {
        err = dlerror();
        printf("Load libprint.so failed : %s \n", err);
        exit(1);
    }
    //clear error info
    dlerror();

    typedef void (*pf_t)(void);
    pf_t print = (pf_t)dlsym(handle, "printExtern");

    err = dlerror();

    if(err)
    {
        printf("fail to find function : %s \n", err);
        exit(1);
    }

    print(); //使用函数指针的方式引用,不能直接引用

    //close the lib
    dlclose(handle);
    if(dlerror())
    {
        printf("close libprint.so failed : %s \n", dlerror());
        exit(1);
    }

    printf("%s\n", __FUNCTION__);

}


int main()
{
    printf("waite 5 seconds\n");
    sleep(5);

    test();
    return 0;
}

同时我们gcc 中还需要加上 libdl.so库

gcc -fPIC -c main.c -o main.o
gcc -o target main.c -L./ -lprint -I./ -lstdc++ -ldl

此时我们删除 libprint.so。然后再执行target 文件。发现程序运行了一段时间,到使用库时才报错

Load libprint.so failed : ./libprint.so: cannot open shared object file: No such file or directory 

由此我们可以看出动态库的显示连接才能真正实现使用时才去调用。

附录 : makefile

在这里我给出这个小工程的makefile

CXX = g++
CC = gcc
FLAGS = -fPIC
TARGET = target
CSOURCE = $(wildcard ./*.c)
COBJS = $(CSOURCE:.c=.o)

target: $(COBJS) libprint 
	$(CC) $(FLAGS) -o $(TARGET) main.o -L./ -lprint -I./ -ldl

%.c:%.o
	$(CC) $(FLAGS) -c $< -o $@

libprint:
	$(CC) $(FLAGS) -shared -o libprint.so print.o

rm:
	rm print.o

clean:
	-rm -rf $(COBJS) $(TARGET) libprint.so

test:
	@echo $(CSOURCE)
	@echo $(COBJS)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/187491.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • php的Allowed memory size of 134217728 bytes exhausted问题解决办法

    php的Allowed memory size of 134217728 bytes exhausted问题解决办法php的Allowed memory size of 134217728 bytes exhausted问题解决办法

    2022年4月24日
  • 快速批量去除图片水印方法大全~~

    原文地址:http://blog.163.com/simonyao_cool/blog/static/16512555720107311858809/

    2022年4月7日
  • php elasticsearch_php get

    php elasticsearch_php getweblogic环境下getRealPath()calledwithunsafe问题的处理环境:weblogic,portal4.5问题:访问页面的时候没有任何错误,只是在mydomain.log里面经常会发现下面的错误信息<2007-8-29下午05时21分06秒CST>=2268995,name=wzportal,context-path=/wzportal)]ge…

    2022年9月19日
  • SPSS(十五)spss之聚类分析(图文+数据集)[通俗易懂]

    SPSS(十五)spss之聚类分析(图文+数据集)[通俗易懂]SPSS(十五)spss之聚类分析(图文+数据集)聚类分析简介按照个体(记录)的特征将它们分类,使同一类别内的个体具有尽可能高的同质性,而类别之间则具有尽可能高的异质性。为了得到比较合理的分类,首先要采用适当的指标来定量地描述研究对象之间的联系的紧密程度。假定研究对象均用所谓的“点”来表示。在聚类分析中,一般的规则是将“距离”较小的点归为同一类,将“距离”较大的点归为不同的类。常见…

    2022年10月18日
  • 免费google代理服务器_google服务器ip地址

    免费google代理服务器_google服务器ip地址有些朋友在登陆谷歌时遇到异常活动而出验证,但是无论怎么输入手机号谷歌都提示此手机号无法用于验证,这种问题大多人都会出现,滥用代理基本都会出现异常验证活动的.谷歌过程中过不了手机号验证,但是在网络上搜一大堆教程也没有用,说什么用QQ邮箱注册、网易邮箱注册等等,这些方法都烂大街了,滥用这些APP接口注册都会被谷歌封号处理的,请大家不要被网上诸多教程误解…

    2022年9月29日
  • Java:详解Java中的异常(Error与Exception)[通俗易懂]

    Java:详解Java中的异常(Error与Exception)[通俗易懂]一、异常机制的概述异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误。(1)编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。(2)运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。(3)…

    2022年7月7日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号