什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
因为当涉及到链接器错误时,人们似乎都在关注这个问题,所以我将在这里添加这个问题。
GCC 5.2.0中出现链接器错误的一个可能原因是现在默认选择了新的libstdc++库ABI。
如果您在std::__cxx11命名空间或标记[abi:cx11]中获得了有关符号未定义引用的链接器错误,则可能表明您正在尝试将使用_GLIBCXX_USE_cxx11_abi宏的不同值编译的对象文件链接在一起。这通常发生在链接到使用旧版本GCC编译的第三方库时。如果无法使用新的ABI重建第三方库,则需要使用旧的ABI重新编译代码。
因此,如果在5.1.0之后切换到GCC时突然出现链接器错误,这将是一件值得检查的事情。
其他回答
不同的架构
您可能会看到这样的消息:
library machine type 'x64' conflicts with target machine type 'X86'
在这种情况下,这意味着可用符号用于不同于您正在编译的体系结构。
在Visual Studio上,这是由于错误的“平台”,您需要选择正确的平台或安装正确版本的库。
在Linux上,这可能是由于错误的库文件夹(例如,使用lib而不是lib64)。
在MacOS上,可以选择在同一文件中传送两种体系结构。可能是链接希望两个版本都存在,但只有一个版本存在。也可能是库所在的lib/lib64文件夹错误。
我正在构建一个共享/动态库。它在Linux和*BSD上运行,但在Mac OS X上,完全相同的编译和链接命令会产生未解决的引用错误。有什么好处?
Mac OS X在内部与Linux和*BSD非常不同。对象/可执行文件格式为
在Linux和*BSD上,当构建共享库时,默认情况下允许未解析的引用。期望它们在加载时能够满足主可执行文件和/或其他共享库的要求。如果在加载时无法解析这些符号,则共享库将无法加载。
在Mac OS X上,构建动态库时,默认情况下不允许未解析的引用。如果希望在加载时解析引用,则需要显式启用未解析的引用。这是使用未定义的dynamic_lookup链接器标志完成的。
在构建可加载插件时,允许未解析的引用非常有用。
在链接共享库时,请确保未隐藏使用的符号。
gcc的默认行为是所有符号都可见。但是,当使用选项-fvisibility=hidden构建转换单元时,只有标记为__attribute__((可见性(“默认”))的函数/符号在生成的共享对象中是外部的。
您可以通过调用以下命令来检查要查找的符号是否为外部符号:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
隐藏/本地符号用小写符号类型的nm表示,例如t而不是代码段的“t”:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
您还可以使用nm和选项-C来定义名称(如果使用了C++)。
与Windows DLL类似,可以使用define标记公共函数,例如DLL_public定义为:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
大致对应于Windows的/MSVC版本:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
有关可见性的更多信息可以在gcc wiki上找到。
当使用-fvisibility=hidden编译翻译单元时,生成的符号仍然具有外部链接(以大写符号类型显示,单位为nm),如果对象文件成为静态库的一部分,则可以毫无问题地用于外部链接。只有当对象文件链接到共享库中时,链接才会变为本地链接。
要查找对象文件中隐藏的符号,请运行:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
当我们在程序中引用了对象名(类、函数、变量等),并且链接器试图在所有链接的对象文件和库中搜索它时无法找到它的定义时,就会出现“Undefined Reference”错误。
因此,当链接器找不到链接对象的定义时,它会发出“未定义引用”错误。从定义中可以清楚地看出,这种错误发生在链接过程的后期。导致“未定义引用”错误的原因多种多样。
一些可能的原因(更频繁):
#1) 没有为对象提供定义
这是导致“未定义引用”错误的最简单原因。程序员只是忘记了定义对象。
考虑以下C++程序。这里我们只指定了函数的原型,然后在主函数中使用了它。
#include <iostream>
int func1();
int main()
{
func1();
}
输出:
main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status
因此,当我们编译此程序时,会发出链接器错误,该错误表示“未定义对‘func1()’的引用”。
为了消除这个错误,我们通过提供函数func1的定义来如下更正程序。现在程序给出了适当的输出。
#include <iostream>
using namespace std;
int func1();
int main()
{
func1();
}
int func1(){
cout<<"hello, world!!";
}
输出:
hello, world!!
#2) 使用的对象定义错误(签名不匹配)
“未定义引用”错误的另一个原因是我们指定了错误的定义。我们在程序中使用任何对象,其定义都不同。
考虑以下C++程序。这里我们调用了func1()。它的原型是int func1()。但其定义与原型不符。如我们所见,函数的定义包含函数的参数。
因此,当编译程序时,由于原型和函数调用匹配,编译是成功的。但是,当链接器试图将函数调用与其定义链接时,它会发现问题并将错误作为“未定义引用”发出。
#include <iostream>
using namespace std;
int func1();
int main()
{
func1();
}
int func1(int n){
cout<<"hello, world!!";
}
输出:
main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status
因此,为了防止这种错误,我们只需交叉检查程序中所有对象的定义和用法是否匹配。
#3) 对象文件未正确链接
此问题还可能导致“未定义引用”错误。在这里,我们可能有多个源文件,我们可以独立编译它们。这样做时,对象链接不正确,导致“未定义引用”。
考虑以下两个C++程序。在第一个文件中,我们使用了第二个文件中定义的“print()”函数。当我们分别编译这些文件时,第一个文件为打印函数提供“未定义引用”,而第二个文件为主函数提供“不定义引用”。
int print();
int main()
{
print();
}
输出:
main.cpp:(.text+0x5): undefined reference to 'print()'
collect2: error ld returned 1 exit status
int print() {
return 42;
}
输出:
(.text+0x20): undefined reference to 'main'
collect2: error ld returned 1 exit status
解决此错误的方法是同时编译两个文件(例如,使用g++)。
除了已经讨论过的原因之外,“未定义的引用”也可能因为以下原因而发生。
#4) 错误的项目类型
当我们在visual studio等C++IDE中指定错误的项目类型,并尝试做项目不期望的事情时,我们就会得到“未定义的引用”。
#5) 没有库
如果程序员没有正确指定库路径,或者完全忘记指定它,那么我们将为程序从库中使用的所有引用获得一个“未定义引用”。
#6) 未编译从属文件
程序员必须确保我们事先编译项目的所有依赖项,以便在编译项目时,编译器找到所有依赖项并成功编译。如果缺少任何依赖项,那么编译器将给出“未定义的引用”。
除了上面讨论的原因之外,“未定义引用”错误还可能在许多其他情况下发生。但底线是程序员搞错了,为了防止这种错误,应该纠正这些错误。
UNICODE定义不一致
Windows UNICODE构建时,TCHAR等被定义为wchar_t等。如果不使用UNICODE进行构建,则TCHAR被定义为char等。这些UNICODE和_UNICODE定义会影响所有“t”字符串类型;LPTSTR、LPCTSTR及其麋鹿。
构建一个定义了UNICODE的库,并试图将其链接到未定义UNICODE项目中,将导致链接器错误,因为TCHAR的定义将不匹配;char与wchar_t。
错误通常包括一个带有char或wchar_t派生类型的值的函数,这些类型也可能包括std::basic_string<>等。浏览代码中受影响的函数时,通常会引用TCHAR或std::basic_string<TCHAR>等。这是一个信号,表明代码最初用于UNICODE和多字节字符(或“窄”)构建。
要更正此问题,请使用UNICODE(和_UNICODE)的一致定义构建所有必需的库和项目。
这可以通过以下两种方式实现:;#定义UNICODE#定义UNICODE或在项目设置中;项目财产>常规>项目默认值>字符集或在命令行上;/DUNICODE/D-UNICODE
如果不打算使用UNICODE,请确保未设置定义,和/或在项目中使用了多字符设置,并始终应用。
不要忘记在“发布”和“调试”版本之间保持一致。