在程序编译过程中,经常会遇到未定义的问题。本文尝试总结几种常见的导致未定义问题的情况,可以帮助用户在遇到相关问题时,方便排查。
本文以一个简单示例,来说明以上遇到的各种情况。
// utils.cpp
int add2(int a, int b) {
return a + b;
}
// global.hpp
int add2(int a, int b);
// main.cpp
#include "global.hpp"
int main() {
int a = 1;
int b = 2;
int c = add2(a, b);
return 0;
}
使用指令如下:
# 编译动态库
$ g++ -shared -fPIC -o libutils.so utils.cpp
$ g++ main.cpp -L. -lutils
# 查询符号表
$ readelf -s --wide libutils.so | grep add
或
$ nm -C libutils.so | grep add
常见情形罗列如下:
这是一般是使用者的问题,比较容易排查。
这是最容易被忽略掉的因素。在尝试各种原因都无法解决的情况下,可以看一下函数定义或函数声明是否一致:
// utils.cpp
extern "C" int add2(int a, int b) {
return a + b;
}
// global.hpp
int add2(int a, int b);
常见情形为:
这就导致函数定义按C代码原则进行解析,致使编译出来的symbol是未经过重整,此时symbol为 add2
。而函数声明未通过 extern “C” 修饰,因此用户在调用时会按照C++代码原则按重整过的名称进行查找,此时 symbol为 _Z4add2ii
。因此就会造成未定义的问题。
$ readelf -s --wide libutils.so | grep add
5: 00000000000010f9 24 FUNC GLOBAL DEFAULT 10 add2
$ readelf -s --wide libutils.so | grep add
5: 00000000000010f9 24 FUNC GLOBAL DEFAULT 10 _Z4add2ii
// utils.cpp
static int add2(int a, int b) {
return a + b;
}
// global.hpp
int add2(int a, int b);
static 会使定义仅对当前文件可见。当函数定义添加 static 修饰时,编译出来的符号表如下:
$ readelf -s --wide libutils.so | grep add
31: 00000000000010f9 24 FUNC LOCAL DEFAULT 10 _ZL4add2ii
可以看到,该符号的可见范围是 LOCAL,此时,该symbol 不能被其他文件所访问到。
// utils.cpp
__attribute__ ((visibility ("hidden")))
int add2(int a, int b) {
return a + b;
}
// global.hpp
int add2(int a, int b);
可以通过 __attribute__ ((visibility ("hidden")))
为函数定义设置可见范围,当设置为 hidden时,该函数定义仅对当前文件可见。编译出来的符号表如下:
$ readelf -s --wide libutils.so | grep add
41: 00000000000010f9 24 FUNC LOCAL DEFAULT 10 _Z4add2ii
可以看到,该符号的可见范围是 LOCAL,此时,该symbol 不能被其他文件所访问到。
除了在代码中设置,也可以通过CMake 设置
add_library(hidden_object OBJECT hidden.c)
set_property(TARGET hidden_object PROPERTY CXX_VISIBILITY_PRESET hidden)
set_property(TARGET hidden_object PROPERTY POSITION_INDEPENDENT_CODE ON)
比如有符号表文件,名称为 libutils.map,内容如下:
LIBUTILS_1.0 {
global:
_Z5mult2ii;
local:
*;
};
// utils.cpp
int add2(int a, int b) {
return a + b;
}
int mult2(int a, int b) {
return a * b;
}
尝试导出部分符号表,编译指令为:
$ g++ -shared -fPIC -o libutils.so utils.cpp -Wl,--version-script=libutils.map
使用readelf 查看符号表:
$ readelf -s --wide libutils.so | grep _Z
5: 0000000000001111 23 FUNC GLOBAL DEFAULT 12 _Z5mult2ii@@LIBUTILS_1.0
43: 00000000000010f9 24 FUNC LOCAL DEFAULT 12 _Z4add2ii
44: 0000000000001111 23 FUNC GLOBAL DEFAULT 12 _Z5mult2ii
此时,可以看到 add 的可见性为 LOCAL,mult2 的可见性为 GLOBAL。因此外部文件可以访问到mult2,不能访问到 add2。