undefined reference 或 undefined symbol 的常见情形汇总

文章目录

  • 1. api 调用不正确
  • 2. 函数定义或函数声明不一致
  • 3. extern "C" 问题
  • 4. 函数被 static 修饰
  • 5. 可见性设置为hidden
  • 6. 符号表未导出相关符号

在程序编译过程中,经常会遇到未定义的问题。本文尝试总结几种常见的导致未定义问题的情况,可以帮助用户在遇到相关问题时,方便排查。

本文以一个简单示例,来说明以上遇到的各种情况。

// 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

常见情形罗列如下:

1. api 调用不正确

这是一般是使用者的问题,比较容易排查。

2. 函数定义或函数声明不一致

这是最容易被忽略掉的因素。在尝试各种原因都无法解决的情况下,可以看一下函数定义或函数声明是否一致:

  • 要注意名称是否一致,比如大小写是否一致。
  • 要注意参数类型是否一致。

3. extern “C” 问题

// utils.cpp
extern "C" int add2(int a, int b) {
    return a + b;
}

// global.hpp
int add2(int a, int b);

常见情形为:

  • 函数定义被 extern “C” 所修饰
  • 而函数声明未通过 extern “C” 修饰

这就导致函数定义按C代码原则进行解析,致使编译出来的symbol是未经过重整,此时symbol为 add2。而函数声明未通过 extern “C” 修饰,因此用户在调用时会按照C++代码原则按重整过的名称进行查找,此时 symbol为 _Z4add2ii。因此就会造成未定义的问题。

  • 添加 extern “C” 修饰的部分会按 C代码进行解析,只有函数名,而没有函数参数列表。
$ readelf -s --wide libutils.so | grep add
     5: 00000000000010f9    24 FUNC    GLOBAL DEFAULT   10 add2
  • 未添加extern “C” 修饰的, 会按C++代码进行解析,会经过名称重整(name mangling),此时名称中是含有参数信息的。
$ readelf -s --wide libutils.so | grep add
     5: 00000000000010f9    24 FUNC    GLOBAL DEFAULT   10 _Z4add2ii

4. 函数被 static 修饰

// 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 不能被其他文件所访问到。

5. 可见性设置为hidden

// 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)

6. 符号表未导出相关符号

比如有符号表文件,名称为 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。

你可能感兴趣的:(c++,linker,linux,c/c++)