深入Os--动态链接

1.动态链接库的使用
动态库支持以两种模式使用,一种模式下,在程序加载运行时,完成动态链接。一种模式下,在程序运行中,完成动态链接。
1.1.程序加载运行时完成动态链接
我们通过一个实例介绍程序加载运行时,使用动态库的方式
在这里插入图片描述

(1). 构建动态库
动态库源文件及makefile位于dynamic
a.t1.cpp

// t1.cpp
int addcnt = 0;
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

b.t2.cpp

// t2.cpp
int mulcnt = 0;
void multvec(int *x, int *y, int *z, int n)
{
    int i;
    mulcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] * y[i];
}

c.makefile

main: t1 t2 dynamic

t1:
	g++ -fpic -std=c++11 t1.cpp -c
t2:
	g++ -fpic -std=c++11 t2.cpp -c

dynamic:
	g++ -std=c++11 -shared t1.o t2.o -o libt.so

clean:
	rm *.o libt.so *.txt

d.通过执行make完成构建。注意编译动态库源文件时需指定-fpic,基于.o得到动态库需指定-shared

(2).提供动态库导出符号声明文件
动态库导出符号声明文件放在include。
a.t.h

#ifndef _T_H
#define _T_H
extern int addcnt;
void multvec(int *x, int *y, int *z, int n);
void addvec(int *x, int *y, int *z, int n);
#endif

上述除了导出函数,我们还导出了变量addcnt。变量的声明需加上extern,否则会被视为变量定义。

(3).主程序使用动态库导出符号
a.主程序为main.cpp

#include <stdio.h>
#include "t.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

int main()
{
    addvec(x, y, z, 2);
    printf("z=[%d %d]\n", z[0], z[1]);
    printf("addcnt_%d\n", addcnt);
    return 0;
}

我们采用加载运行时完成动态链接方式使用动态库时,在使用动态库导出符号时,需要先声明符号。然后直接使用即可。
上述使用了动态库导出的addvec,addcnt
b.构建可执行程序的makefile

main:
	g++ main.cpp -std=c++11 -I./include -L./dynamic -lt
clean:
	rm a.out *.o *.txt

我们采用加载运行时完成动态链接方式使用动态库时,构建可执行程序时,需通过-L -l来指定要链接的动态库的位置信息。-I用于指定编译期间头文件搜索路径。

(4).启动可执行程序
若上述编译完毕后,我们直接在a.out所在目录通过命令行执行./a.out是不行的。
在这里插入图片描述
因为类似编译链接过程需通过-L -l来指定要链接的动态库的位置信息。加载运行时,可以通过设置LD_LIBRARY_PATH来指定要链接的动态库的位置信息。上述结构下,我们提供s.sh。

// s.sh
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./dynamic
./a.out

这样执行./s.sh即可正常启动。LD_LIBRARY_PATH用于在程序启动运行时告知搜索程序依赖的动态库的路径。
要查看可执行程序依赖那些动态库,可使用ldd a.out
在这里插入图片描述

1.2.程序运行期间完成动态链接
我们通过一个实例介绍程序运行期间,使用动态库的方式。
在这里插入图片描述
(1). 构建动态库
和1.1部分相同。

(2).主程序中使用动态库导出符号
注意,运行期间使用动态库时,我们并不需要动态库导出符号声明文件。
因为使用导出符号的方式是通过dlsym直接取得导出符号地址后,转换为相应类型后使用。
a.主程序为main.cpp
这里的main.cpp放置在demo下

#include <iostream>
#include <dlfcn.h>

int x[2] = {1,2};
int y[2] = {3,4};
int z[2];

typedef void (*AddVec)(int*, int*, int*, int);
int main()
{
    void *handle;
    AddVec addvec = nullptr;
    char *error;
    handle = dlopen("libt.so", RTLD_LAZY);
    if(!handle)
    {
        printf("%s\n", dlerror());
        return 0;
    }

    addvec = (AddVec)dlsym(handle, "addvec");
    if((error = dlerror()) != NULL)
    {
        printf("%s\n", error);
        dlclose(handle);
        return 0;
    }

    int* addcnt = (int*)dlsym(handle, "addcnt");
    if((error = dlerror()) != NULL)
    {
        printf("%s\n", error);
        dlclose(handle);
        return 0;
    }

    addvec(x, y, z, 2);
    printf("z = [%d %d],cnt_%d\n", z[0], z[1], *addcnt);
    dlclose(handle);
    return 0;
}

我们采用运行期间完成动态链接的方式使用动态库,在使用动态库导出符号时,通过dlsym取得导出符号地址后,转换为匹配类型后,即可使用。上述使用了动态库导出的addvec,addcnt

b.构建可执行程序的makefile
makefile放置在demo。

main:
	g++ -std=c++11 -rdynamic main.cpp -I../include -ldl
clean:
	rm a.out *.o *.txt

我们采用运行期间完成动态链接方式使用动态库时,构建可执行程序时,不需要通过-L -l来指定要链接的动态库的位置信息。因为编译链接过程尚未用到运行期间要链接的动态库。但需指定-rdynamic -ldl,因为我们此时需要链接到服务于运行期间动态连接的动态库dl。
在这里插入图片描述
(3).启动可执行程序
类似的,我们在启动前需通过LD_LIBRARY_PATH来指定dlopen中搜索动态库的路径信息。
我们的放置在demo下的s.sh为

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../dynamic
./a.out

但执行./s.sh时,报错了:
在这里插入图片描述
因为我们采用c++方式编译动态库时,库内addvec的符号实际编译出的符号名称为:
在这里插入图片描述
这是因为c++编译器对编译时,针对函数类型会结合其形参为其构建符号名称。c编译器不会。
c++支持同名函数重载,所以,这样是需要的。c不支持同名函数重载,所以,不需要。

上述报错是因为我们通过dlsym取出addvec符号地址时,通过名称addvec在动态库中找不到匹配的符号。
为了正常使用dlsym取得导出符号地址:
(1).我们要么将dlsym传入的符号名修改为_Z6addvecPiS_S_i
(2).要么通过设置使得c++编译时,针对addvec导出符号不要采用符号重新命名机制。我们只需在动态库源文件符号定义处,添加extern "C"修饰即可。若我们采取了此种方式,应该同步在类库导出符号声明文件中为addvec的声明也添加extern "C"修饰。这样,1.1中使用动态库时,也会直接采用addvec来在动态库中定位符号的定义位置。

针对变量类型导出符号,如addcnt,c++编译器不会对符号执行重新命名。所以,直接使用符号名即可。

值得注意的是,添加extern "C"后由于关闭结合形参重命名机制,所以,此时也就不允许同名符号重载了。

int addcnt = 0;
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

void addvec(int *x, int *y, int *z)
{
    int i;
    i = 0;
    i++;
}

上述内容,作为t1.cpp内容时,可正常编译。

int addcnt = 0;
extern "C" void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

extern "C" void addvec(int *x, int *y, int *z)
{
    int i;
    i = 0;
    i++;
}

上述内容作为t1.cpp内容时,无法编译通过。因为存在同名符号问题。
在这里插入图片描述

相关推荐

  1. cmake 动态库命令

    2023-12-06 16:06:07       56 阅读
  2. 开源协议及静态动态

    2023-12-06 16:06:07       31 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-06 16:06:07       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-06 16:06:07       100 阅读
  3. 在Django里面运行非项目文件

    2023-12-06 16:06:07       82 阅读
  4. Python语言-面向对象

    2023-12-06 16:06:07       91 阅读

热门阅读

  1. C++字符去除空格反转顺序输出

    2023-12-06 16:06:07       53 阅读
  2. 数据结构-堆

    2023-12-06 16:06:07       58 阅读
  3. 基数排序简单了解

    2023-12-06 16:06:07       59 阅读
  4. 基于Hadoop的异构网络协同过滤推荐算法设计

    2023-12-06 16:06:07       49 阅读
  5. 可以使用京东商品详情 API 获取哪些商品信息?

    2023-12-06 16:06:07       61 阅读
  6. Gson与FastJson详解

    2023-12-06 16:06:07       47 阅读
  7. (C++20) constinit常量初始化

    2023-12-06 16:06:07       59 阅读
  8. P5706 【深基2.例8】再分肥宅水

    2023-12-06 16:06:07       63 阅读
  9. AIOps、微服务和云平台

    2023-12-06 16:06:07       56 阅读
  10. 做题笔记:SQL Sever 方式做牛客SQL的题目--VQ29

    2023-12-06 16:06:07       55 阅读
  11. 蓝桥杯官网练习题(平均)

    2023-12-06 16:06:07       59 阅读
  12. vscode配置代码片段

    2023-12-06 16:06:07       54 阅读
  13. rocketmq 集群环境部署及与spring cloud 集成

    2023-12-06 16:06:07       54 阅读
  14. RepidJson将内容写入文件简单代码示例

    2023-12-06 16:06:07       55 阅读