什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
函数或类方法在源文件中使用内联说明符定义。
例如:-
主.cpp
#include "gum.h"
#include "foo.h"
int main()
{
gum();
foo f;
f.bar();
return 0;
}
foo.h(1)
#pragma once
struct foo {
void bar() const;
};
口香糖.h(1)
#pragma once
extern void gum();
foo.cpp(1)
#include "foo.h"
#include <iostream>
inline /* <- wrong! */ void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
gum.cpp(1)
#include "gum.h"
#include <iostream>
inline /* <- wrong! */ void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
如果指定gum(类似地,foo::bar)在其定义中是内联的,那么编译器将通过以下方式内联gum(如果它选择):-
没有任何独特的口香糖定义,因此不发出任何符号,链接器可以通过该符号引用口香糖的定义,而是将所有对gum的调用替换为编译后的gum主体的内联副本。
因此,如果在源文件gum.cpp中内联定义gum,则编译为对象文件gum.o,其中所有对gum的调用都是内联的并且没有定义接头可以指代口香糖的符号。当你将gum.o与另一个对象文件(例如main.o)链接到程序中引用外部符号gum时,链接器无法解析这些参考文献。因此连杆失效:
编译:
g++ -c main.cpp foo.cpp gum.cpp
链接:
$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status
如果编译器可以在调用gum的每个源文件中看到它的定义,则只能将gum定义为内联。这意味着它的内联定义需要存在于包含在每个源文件中的头文件中您可以在其中调用gum。做两件事之一:
要么不内联定义
从源文件定义中删除内联说明符:
foo.cpp(2)
#include "foo.h"
#include <iostream>
void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
gum.cpp(2)
#include "gum.h"
#include <iostream>
void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
重新生成:
$ g++ -c main.cpp foo.cpp gum.cpp
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const
成功
或正确内联
头文件中的内联定义:
foo.h(2)
#pragma once
#include <iostream>
struct foo {
void bar() const { // In-class definition is implicitly inline
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
// Alternatively...
#if 0
struct foo {
void bar() const;
};
inline void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif
口香糖.h(2)
#pragma once
#include <iostream>
inline void gum() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
现在我们不需要foo.cpp或gum.cpp:
$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const
其他回答
当您使用错误的编译器构建程序时
如果您使用的是gcc或clang编译器套件,则应根据所使用的语言使用正确的编译器驱动程序。使用g++或clang++编译和链接C++程序。改用gcc或clang将导致对C++标准库符号的引用未定义。例子:
$ gcc -o test test.cpp
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: warning: relocation against `_ZSt4cout' in read-only section `.text'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: in function `main': test.cpp:(.text+0xe): undefined reference to `std::cout'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: test.cpp:(.text+0x13): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: in function `__static_initialization_and_destruction_0(int, int)':
test.cpp:(.text+0x43): undefined reference to `std::ios_base::Init::Init()'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: test.cpp:(.text+0x58): undefined reference to `std::ios_base::Init::~Init()'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
当我们在程序中引用了对象名(类、函数、变量等),并且链接器试图在所有链接的对象文件和库中搜索它时无法找到它的定义时,就会出现“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) 未编译从属文件
程序员必须确保我们事先编译项目的所有依赖项,以便在编译项目时,编译器找到所有依赖项并成功编译。如果缺少任何依赖项,那么编译器将给出“未定义的引用”。
除了上面讨论的原因之外,“未定义引用”错误还可能在许多其他情况下发生。但底线是程序员搞错了,为了防止这种错误,应该纠正这些错误。
在链接共享库时,请确保未隐藏使用的符号。
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
如果所有其他操作都失败,请重新编译。
最近,我只需重新编译有问题的文件,就可以消除Visual Studio 2012中未解决的外部错误。当我重新构建时,错误消失了。
当两个(或多个)库具有循环依赖关系时,通常会发生这种情况。库A尝试使用B.lib中的符号,库B尝试使用A.lib中的字符。两者都不存在。当您尝试编译A时,链接步骤将失败,因为它找不到B.lib。将生成A.lib,但不会生成dll。然后编译B,这将成功并生成B.lib。重新编译A现在可以工作了,因为现在找到了B.lib。
此外,如果您正在使用第三方库,请确保您拥有正确的32/64位二进制文件