cmake文件中SHARED和MODULE库在MacOS上的差异

news/2024/10/31 1:31:08/

cmake文件中SHARED和MODULE

在cmake中可以使用add_library函数生成静态库(STATIC)、动态库(SHARED)和模块库(MODULE)三种。在MacOS上,对应生成的文件类型分别为:.a .dylib .so

动态库链接和重定位的过程

  • 1、加载库:操作系统根据库的路径将库加载到内存中,并将库中的符号地址保存在一个全局的符号表中。加载库时,操作系统会执行一些初始化工作,如分配内存空间、建立虚拟地址映射等。
  • 2、程序链接:程序运行时,操作系统会将程序中引用的库中的符号地址替换成实际的地址。这个过程称为链接。链接的过程包括两个步骤:符号查找和符号重定位。
  • 3、符号查找:当程序引用某个库中的符号时,操作系统首先在全局符号表中查找该符号的地址。如果找到了该符号,操作系统会将该符号的地址保存在程序中的重定位表中。
  • 4、符号重定位:当程序运行时,重定位表中保存的地址需要进行重定位。操作系统会将重定位表中的地址加上库的基地址,得到最终的符号地址。这个过程称为符号重定位。
  • 5、重定位完成:完成符号地址的计算和修改后,程序即可正常运行。

以上操作通常由操作系统的动态链接器完成。在Linux系统中动态链接器默认为ld.so,而在MacOS上则是dyld(Dynamic Linker)

.dylib文件和.so文件的区别

  • 1、文件格式:.dylib文件是Mach-O格式的动态链接库文件,而.so文件是ELF格式的动态链接库文件。(Mach-O格式是macOS系统中使用的可执行文件格式,而ELF格式则是Unix/Linux系统中使用的可执行文件格式)
  • 2、版本控制:.dylib文件支持使用版本控制符号来管理和控制库的版本,在.dylib文件中,每个符号都可以包含一个版本号,而.so文件则不支持。这意味着.dylib文件可以在不破坏二进制兼容性的情况下更新库的版本,而.so文件则需要在更新版本时重新编译和链接程序。
例如,假设我们有一个.dylib文件名为libtest.dylib,它的当前版本号为1.0,兼容版本号为1.0.0,
我们可以使用以下命令编译程序并链接到libtest.dylib文件,并指定需要使用的符号版本:
gcc -o myprog myprog.c -L/path/to/lib -ltest -Wl,-current_version,1.0,-compatibility_version,1.0.0
  • 3、符号绑定:.dylib文件支持懒惰绑定和弱符号绑定。懒惰绑定是指在需要使用符号时才进行符号绑定,从而提高了程序的启动速度和内存使用效率。而弱符号绑定是指在符号未定义时也不会引发链接错误,而是将其绑定到一个默认值上。这些特性可以使得.dylib文件的使用更为灵活和高效。(下面有例子详细说明该特性)
  • 4、延迟绑定:.dylib文件支持延迟绑定符号,也就是在程序运行时才进行符号绑定。延迟绑定可以减少程序启动时间和内存使用,特别是对于大型程序和复杂的.dylib文件,效果更为明显。在.dylib文件中,可以使用LD_BIND_NOW环境变量或dyld命令行选项来控制符号的绑定时间。

库中符号的可见性(以.dylib举例)

例子:

// 计算一个数平方根的简单程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "TutorialConfig.h"__attribute__((visibility("hidden")))
int global_var = 4;__attribute__((visibility("hidden")))
int test_func(const char* str)
{if (strlen(str) > 0)printf("In test_func, args: %s", str);return 0;
} int main (int argc, char *argv[]) {if (argc < 2) {printf("%s Version %d.%d\n", argv[0],Tutorial_VERSION_MAJOR,Tutorial_VERSION_MINOR);printf("Usage: %s number\n",argv[0]);return 1;}double inputValue = atof(argv[1]) * global_var;test_func("haha");double outputValue = sqrt(inputValue);printf("The square root of %g is %g\n", inputValue, outputValue);return 0;
}

编译生成libTutorial.dylib后使用"nm libTutorial.dylib"命令查看其符号如下:

0000000000003db8 t __Z9test_funcPKc
0000000000008018 d __dyld_privateU _atof
0000000000008020 d _global_var
0000000000003e08 T _mainU _printfU _strlenU dyld_stub_binder

可以看到__Z9test_funcPKc,因为nm命令显示的符号通常是经过链接器处理后的符号,也称为外部符号(External Symbol),而原始符号(Raw Symbol)是在编译器生成目标文件时产生的符号。链接器在将目标文件链接成可执行文件或共享库时,会将原始符号转换为外部符号,并进行符号分辨和符号解析等操作。
所以我们可以使用 objdump -t 或者 c++filt 查看原始符号,这里我使用 nm libTutorial.dylib | c++filt 查看的结果如下:

0000000000003db8 t test_func(char const*)
0000000000008018 d __dyld_privateU _atof
0000000000008020 d _global_var
0000000000003e08 T _mainU _printfU _strlenU dyld_stub_binder

上面的一些符号“t T d U” 和 __dyld_private、dyld_stub_binder 我解释一下:

U类型符号:U表示未定义(Undefined),即该符号在当前模块中未定义,但在其他模块中定义了。通常情况下,链接器会在链接时将未定义的符号与其他模块中定义的符号进行匹配。如果找不到匹配的符号,则会报链接错误。
d类型符号:d表示已定义的数据段(Defined data)。这种类型的符号表示当前模块中定义了一个全局变量或静态变量。
D类型符号:D表示已定义的数据段(Defined data)。与d类型符号类似,但这种类型的符号表示当前模块中定义了一个公共变量或公共静态变量。
t类型符号:t表示已定义的文本段(Defined text)。这种类型的符号表示当前模块中定义了一个函数或静态函数。
T类型符号:T表示已定义的文本段(Defined text)。与t类型符号类似,但这种类型的符号表示当前模块中定义了一个公共函数或公共静态函数。
r类型符号:r表示只读数据段(Read-only data)。这种类型的符号表示当前模块中定义了一个只读全局变量或静态变量。
R类型符号:R表示只读数据段(Read-only data)。与r类型符号类似,但这种类型的符号表示当前模块中定义了一个只读公共变量或静态变量。

__dyld_private是动态链接器(dyld)的私有符号。
dyld_stub_binder是一个占位符(stub),该占位符指向动态链接库中函数的符号。当程序运行时,动态链接器会将占位符替换为实际函数的地址,这个过程称为符号绑定(Symbol Binding),这个就是前文谈到的dylib文件和so文件的区别。dyld_stub_binder变量就是在动态链接器中实现符号绑定的函数之一。

在代码中全局变量global_var和函数test_func之前,加上了编译器修饰符__attribute__((visibility(“hidden”)))。将符号的可见性设置为"hidden",表示该符号只能在当前编译单元内部使用,不能被其他编译单元引用,所以在nm查看符号的时候global_var前是d而不是D,test_func前是t而不是T。
当然,我们也可以通过visibility控制要导出哪些符号:

"hidden":将符号的可见性设置为"hidden",表示该符号只能在当前编译单元内部使用,不能被其他编译单元引用。
"internal":将符号的可见性设置为"internal",表示该符号可以被其他编译单元引用,但是只能在当前库中使用,不能被库外的程序使用。
"protected":将符号的可见性设置为"protected",表示该符号可以被其他编译单元引用,并且可以被库外的程序使用,但是只有在动态链接库中才能访问该符号。
"default":将符号的可见性设置为"default",表示该符号可以被其他编译单元引用,并且可以被库外的程序使用。这是默认的可见性级别。
"external":将符号的可见性设置为"external",表示该符号可以被其他编译单元引用,并且可以被库外的程序使用。和"default"可见性级别相比,"external"可见性级别会将符号的可见性增强,使得符号可以被其他库中的符号引用。
需要注意的是,attribute((visibility()))修饰符只在支持可见性控制的编译器中有效,例如GCC、Clang等。在其他编译器中可能不支持该修饰符。

总结

在cmake中,虽然SHARED和MODULE都能生成动态库,但是区别还是挺大的,一定要彻底搞清楚这两者的区别吖~。于细微之处见知著,于无声处听惊雷。


http://www.ppmy.cn/news/67768.html

相关文章

浅谈三次数学危机——费马大定理

浅谈三次数学危机——费马大定理 19世纪末20世纪初&#xff0c;随着非欧几里得几何、无穷小分析等领域的迅速发展&#xff0c;数学界面临着前所未有的挑战。这场关于数学基础的争论&#xff0c;被称为“数学危机”。数学危机起源于对数学的基础概念和公理系统的重新审视&#x…

快速排序算法原理

快速排序算法原理 1、什么是快速排序&#xff1f; 快速排序是一种常用的排序算法&#xff0c;通过分治法的思想&#xff0c;将一个大问题划分为多个小问题&#xff0c;以此实现排序。 2、快速排序的基本原理 选择一个基准元素&#xff08;pivot&#xff09;将数组中小于基准…

Python 列表(List)

Python中的列表(List)是一种有序的集合&#xff0c;可以包含任意数量的元素&#xff0c;元素可以是数字、字符串或其他对象&#xff0c;甚至包含其他列表。 以下是一些常见的列表操作&#xff1a; 1. 创建列表&#xff1a; 要创建一个列表&#xff0c;可以使用方括号 [] 将元…

js为什么会阻塞渲染, 什么是异步?

javaScript 是单线程执行的语言&#xff0c;它的执行机制是基于事件循环模型的。当 JavaScript 执行代码时&#xff0c;如果遇到阻塞&#xff08;如执行时间较长的代码、同步的网络请求、计算密集型操作等&#xff09;&#xff0c;则会阻塞 JavaScript 引擎的执行&#xff0c;直…

口腔污水处理设备工艺流程

口腔污水处理设备工艺流程 该污水处理要经过滤网去除水中杂物&#xff0c;再对废水进行消毒。依据以往的工程经历以及医疗机构水污染物排放标准中的规则&#xff0c;选用过滤消毒对污水进行净化处理污水处理设备的工艺路线断定如下:医院污水一过滤一消毒一排放。 口腔污水外理设…

这个原因,让你自动化测试年薪30W+也不能躺平

其实这个问题&#xff0c;我们遇到到很多次&#xff1a; “自动化就可以满足我现在的公司需求&#xff0c;为什么不躺平&#xff0c;还要继续学测开&#xff1f;” 每次遇到这个问题后&#xff0c;立马就会有一个“涨薪效应”&#xff1a;收到粉丝们的高薪offer ​ 其实&#x…

25岁,本科学历,待业,如何成为优秀的数据分析师,值得关注!

25岁&#xff0c;本科学历&#xff0c;待业&#xff0c;如何成为优秀的数据分析师&#xff0c;值得关注&#xff01; 你是在工作几年后确定自己的职业方向的呢&#xff1f;还是一直都是处于迷茫&#xff0c;随波逐流的状态&#xff1f;都说谁的青春不迷茫&#xff0c;但时间是最…

搭建家庭影音媒体中心 --公网远程连接Jellyfin流媒体服务器

文章目录 前言1. 安装Home Assistant2. 配置Home Assistant3. 安装cpolar内网穿透3.1 windows系统3.2 Linux系统3.3 macOS系统 4. 映射Home Assistant端口5. 公网访问Home Assistant6. 固定公网地址6.1 保留一个固定二级子域名6.2 配置固定二级子域名 转载自远程穿透的文章&…