在cmake中可以使用add_library函数生成静态库(STATIC)、动态库(SHARED)和模块库(MODULE)三种。在MacOS上,对应生成的文件类型分别为:.a .dylib .so
以上操作通常由操作系统的动态链接器完成。在Linux系统中动态链接器默认为ld.so,而在MacOS上则是dyld(Dynamic Linker)
例如,假设我们有一个.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
例子:
// 计算一个数平方根的简单程序
#include
#include
#include
#include
#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_private
U _atof
0000000000008020 d _global_var
0000000000003e08 T _main
U _printf
U _strlen
U 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_private
U _atof
0000000000008020 d _global_var
0000000000003e08 T _main
U _printf
U _strlen
U 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都能生成动态库,但是区别还是挺大的,一定要彻底搞清楚这两者的区别吖~。于细微之处见知著,于无声处听惊雷。