Linux系统下编译和运行程序时候,经常出现动态库链接的问题,主要有两种情况,一种是编译的时候链接问题,另一种是运行的时候链接。
编译链接问题
首先我们要明白C++编译的过程,主要有两步。
-
编译阶段:
编译阶段,编译器会将源码编译为目标文件(.o文件)。编译会将源码中函数转换特定的链接符号。 -
链接阶段
链接阶段,会根据目标文件中链接符号信息,去链接对应动态库,并记录一些信息,如函数的内存地址偏移量等信息。
在链接阶段需要知道要链接的动态库或者静态库的名字和地址,编译器是如何知道的了,答案是CMakeLists.txt中target_link_libraries()
函数
target_link_libraries(my_executable /path/to/*.so)
有的时候target_link_libraries()
只指定了对应动态库函数的名称,并没有指定动态库函数的地址,例如:
target_link_libraries(my_executable gtsam)#链接到gtsam库
target_link_libraries(my_executable ${OpenCV_LIBRARIES})#链接到Opencv的动态库
这时候编译如何找到对应动态库的地址,答案是find_package()
和link_directories()
函数。find_package()
会在动态库对应的cmake文件中设置动态库变量的路径(例如${OpenCV_LIBRARIES}
)。或在在link_directories()
指定路径,例如:
link_directories(/path/to/lib)
因此编译程序的时候如果没有指定链接库的路径或在链接的库的名称,就会报以下两种错误:
-
undefined reference to
这种是因为没有指定要链接的库,即target_link_libraries()
中没有添加对应的动态的名称。源码编译阶段,生成了库函数的链接符号。却在链接的时候找不到具体的实现方式。这里根据undefined reference to的具体内容,来查看是哪个库函数。以_ZN3tbb8internal23allocate_via_handler_v3Em
为例。_ZN3tbb8internal23allocate_via_handler_v3Em
是一个经过 C++ 名字修饰(name mangling)处理的符号名。这个符号名代表了一个特定的函数或变量,具体来说,它是 Intel TBB(Threading Building Blocks)库中的一个内部函数。我们可以通过名字修饰的规则来解析这个符号。
名字修饰解析 -
_Z
: 这是一个前缀,表示这是一个经过修饰的符号。 -
N3tbb
: 这部分表示命名空间。N3 表示命名空间的长度为 3,后面的 tbb 是命名空间的名称。因此,这里表示 tbb 命名空间。 -
8internal
: 这部分表示一个类或结构体的名称。8 表示名称的长度为 8,后面的 internal 是类名。因此,这里表示 internal 类。 -
23allocate_via_handler_v3
: 这部分表示函数的名称。23 表示名称的长度为 23,后面的 allocate_via_handler_v3 是函数的名称。 -
Em
: 这部分表示函数的参数类型。E 表示该函数接受一个 size_t 类型的参数(通常用于表示内存大小),而 m 表示该参数是一个 double 类型(在某些情况下可能表示其他类型,具体取决于上下文)。 -
因此,_ZN3tbb8internal23allocate_via_handler_v3Em 代表的是 tbb::internal::allocate_via_handler_v3(double) 这个函数。这个函数可能用于在 TBB 中处理内存分配的某种方式。符号名根据 命名空间-类名-函数名-函数参数来命名的
当然,也存在链接的库里没有对应的函数,这个也需要考虑对应的函数版本是否正确。通过 ldd -r xxx.so 命令查看so库链接状态和错误信息。
- /usr/bin/ld
/usr/bin/ld报错,是因为指定了要链接库的名称却没有指定其路径。虽然指定了动态库的名称,链接器找不到要链接的库在哪。
find_package(GTSAM REQUIRED QUIET)
target_link_libraries(my_executable gtsam)#链接到gtsam库
例如上述式子,虽然指定了gtsam动态库,并且通过find_package(GTSAM REQUIRED QUIET)
指定了gtsam包的路径。但是gtsam的cmake
文件中并没有指出对应gtsam动态库的路径。有的包cmake
文件并不会指出动态的路径。如果找不到就手动设定,target_link_libraries()
中添加动态库完整路径或在link_directories()
设置链接库的路径。
运行链接问题
程序运行时,需要链接对应的动态库。系统会从LD_LIBRARY_PATH
和/etc/ld.so.conf
配置文件中寻找对应的动态库。如果运行/usr/bin/ld报错,就将对应动态库的路径添加到上述环境变量或在文件中。
在~/.bashrc中添加:
export LD_LIBRARY_PATH=/path/to/your/libs:$LD_LIBRARY_PATH
或在/etc/ld.so.conf
中添加动态库的conf
文件。
注意:环境变量$LD_LIBRARY_PATH和/etc/ld.so.conf中的配置只对运行时有效,编译不是按照这个路径寻找的
运行时undefined refrence to的问题
理论上编译程序的时候链接没有问题,运行的时候应该不会出现undefined refrence to的错误。但是,有时候即使编译成功了也会出现这种问题:
成功链接但在运行时出现未定义符号的情况通常与以下几个因素有关:
-
动态链接库缺失:
- 如果程序依赖于某个动态链接库(shared library),但在运行时找不到该库,或者库的版本不匹配,可能会导致未定义符号的错误。
-
符号的实现缺失:
- 在某些情况下,链接器可能会找到符号的声明(例如,函数原型或类定义),但找不到其实现。例如,某个库的头文件中声明了一个函数,但在链接时没有提供该函数的实现。
-
条件编译:
- 如果代码中使用了条件编译(如
#ifdef
、#ifndef
等),某些符号可能在特定条件下被排除在外,导致在运行时找不到这些符号。
- 如果代码中使用了条件编译(如
-
C++ 名字修饰(Name Mangling):
- 在 C++ 中,函数名会被编译器修饰以支持函数重载。如果链接时使用了不匹配的函数签名,可能会导致未定义符号的错误。
-
库的链接顺序:
- 在某些情况下,链接库的顺序可能会影响符号的解析。如果一个库依赖于另一个库,而链接顺序不正确,可能会导致未定义符号的错误。
-
运行时环境问题:
- 如果程序在不同的环境中运行(例如,开发环境与生产环境),可能会因为环境中缺少某些库或版本不匹配而导致未定义符号的错误。
-
静态链接与动态链接的混淆:
- 如果在编译时使用了静态链接,但在运行时又试图使用动态链接库,可能会导致符号未定义的问题。
-
使用了不兼容的库版本:
- 如果程序依赖于某个库的特定版本,而在运行时使用了不兼容的版本,可能会导致未定义符号的错误。
解决方法
- 检查依赖:使用工具(如
ldd
)检查所有依赖库是否存在。 - 确保符号实现:确保所有声明的符号都有对应的实现。
- 检查条件编译:确认条件编译的宏定义是否正确。
- 检查链接顺序:确保链接库的顺序正确。
- 使用正确的库版本:确保使用的库版本与编译时一致。
通过以上方法,可以有效地排查和解决运行时未定义符号的问题。
undefined symbol问题的查找、定位与解决方法