什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
编译器/IDE中的错误
我最近遇到了这个问题,结果发现这是Visual Studio Express 2013中的一个错误。我不得不从项目中删除一个源文件,然后重新添加它以克服错误。
如果您认为这可能是编译器/IDE中的错误,请尝试以下步骤:
清理项目(一些IDE可以选择这样做,您也可以手动删除对象文件)尝试启动新项目,从原始代码复制所有源代码。
其他回答
需要考虑的一些拼写错误:(我作为初学者经常遇到)
如果您使用的是类:请检查您是否没有在定义函数的cpp文件中的函数名之前忘记“classname::”。如果使用forward声明:请确保声明正确的类型。例如:如果要转发声明“结构”,请使用“结构”而不是“类”。
模板实现不可见。
非专用模板的定义必须对使用它们的所有翻译单位可见。这意味着不能分离模板的定义到实现文件。如果必须分离实现,通常的解决方法是在头的末尾包含一个impl文件声明模板。常见的情况是:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
要解决这个问题,必须将X::foo的定义移动到头文件或使用它的翻译单元可见的某个位置。
专用化模板可以在实现文件中实现,并且实现不必是可见的,但是必须事先声明专用化。
有关进一步的解释和另一种可能的解决方案(显式实例化),请参阅此问题和答案。
编译C++程序分几个步骤进行,如2.2所述(Keith Thompson作为参考):
翻译语法规则之间的优先顺序由以下阶段规定[参见脚注]。物理源文件字符以实现定义的方式映射到基本源字符集(为行尾指示符引入新的行尾字符)如果必需的[剪]将删除紧跟着换行符的反斜杠字符(\)的每个实例,将物理源行拼接到形成逻辑源线。[剪]源文件被分解为预处理标记(2.5)和空白字符序列(包括注释)。[剪]执行预处理指令,展开宏调用,并执行_Pragma一元运算符表达式。[剪]字符文本或字符串文本中的每个源字符集成员,以及每个转义序列和通用字符名在字符文本或非原始字符串文本中执行字符集的对应成员;[剪]连接相邻的字符串文字标记。分隔标记的空白字符不再有效。每个预处理令牌都转换为一个令牌。(2.7)对生成的令牌进行语法和语义分析翻译为翻译单元。[剪]翻译的翻译单元和实例化单元组合如下:[SNIP]解析所有外部实体引用。链接库组件以满足对未在中定义的实体的外部引用当前翻译。所有这样的转换器输出都被收集到包含执行所需信息的程序映像执行环境。(强调矿井)[脚注]尽管在实践中不同的阶段可能会被合并在一起,但实现必须表现得好像这些单独的阶段发生了一样。
指定的错误发生在编译的最后阶段,通常称为链接。这基本上意味着你把一堆实现文件编译成了对象文件或库,现在你想让它们一起工作。
假设您在.cpp中定义了符号a。现在,b.cpp声明了该符号并使用了它。在链接之前,它只是假设该符号是在某个地方定义的,但它并不在乎在哪里。链接阶段负责查找符号并将其正确链接到b.cpp(实际上,链接到使用它的对象或库)。
如果您使用的是Microsoft Visual Studio,您将看到项目生成.lib文件。其中包含导出符号表和导入符号表。导入的符号将根据链接的库进行解析,导出的符号将提供给使用该.lib的库(如果有)。
其他编译器/平台也存在类似的机制。
常见的错误消息包括错误LNK2001、错误LNK1120、错误LNK2019(适用于Microsoft Visual Studio)和未定义的对GCC symbolName的引用。
代码:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
struct A
{
virtual ~A() = 0;
};
struct B: A
{
virtual ~B(){}
};
extern int x;
void foo();
int main()
{
x = 0;
foo();
Y y;
B b;
}
将使用GCC生成以下错误:
/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status
以及Microsoft Visual Studio中的类似错误:
1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
常见原因包括:
未能链接到适当的库/对象文件或编译实现文件已声明和未定义的变量或函数。类类型成员的常见问题模板实现不可见。符号是在C程序中定义的,并在C++代码中使用。跨模块.dll错误地导入/导出方法/类。(特定于MSVS)循环库依赖关系对的未定义引用`WinMain@16'相互依赖的库顺序多个同名源文件使用#pragma(Microsoft Visual Studio)时键入错误或不包含.lib扩展名模板好友问题UNICODE定义不一致常量变量声明/定义中缺少“extern”(仅限C++)未为多文件项目配置Visual Studio代码在Mac OS X上构建dylib时出错,但在其他Unix-y系统上也可以
当我们在程序中引用了对象名(类、函数、变量等),并且链接器试图在所有链接的对象文件和库中搜索它时无法找到它的定义时,就会出现“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) 未编译从属文件
程序员必须确保我们事先编译项目的所有依赖项,以便在编译项目时,编译器找到所有依赖项并成功编译。如果缺少任何依赖项,那么编译器将给出“未定义的引用”。
除了上面讨论的原因之外,“未定义引用”错误还可能在许多其他情况下发生。但底线是程序员搞错了,为了防止这种错误,应该纠正这些错误。
编译器/IDE中的错误
我最近遇到了这个问题,结果发现这是Visual Studio Express 2013中的一个错误。我不得不从项目中删除一个源文件,然后重新添加它以克服错误。
如果您认为这可能是编译器/IDE中的错误,请尝试以下步骤:
清理项目(一些IDE可以选择这样做,您也可以手动删除对象文件)尝试启动新项目,从原始代码复制所有源代码。