什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
Microsoft提供了一个#pragma,以在链接时引用正确的库;
#pragma comment(lib, "libname.lib")
除了库路径(包括库的目录)之外,这应该是库的全名。
其他回答
当我们在程序中引用了对象名(类、函数、变量等),并且链接器试图在所有链接的对象文件和库中搜索它时无法找到它的定义时,就会出现“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) 未编译从属文件
程序员必须确保我们事先编译项目的所有依赖项,以便在编译项目时,编译器找到所有依赖项并成功编译。如果缺少任何依赖项,那么编译器将给出“未定义的引用”。
除了上面讨论的原因之外,“未定义引用”错误还可能在许多其他情况下发生。但底线是程序员搞错了,为了防止这种错误,应该纠正这些错误。
编译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系统上也可以
什么是“未定义的引用/未解析的外部符号”
我将尝试解释什么是“未定义的引用/未解析的外部符号”。
注意:我使用的是g++和Linux,所有示例都是针对它的
例如,我们有一些代码
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
and
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
生成对象文件
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
在汇编程序阶段之后,我们有一个对象文件,其中包含要导出的任何符号。看看这些符号
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
我拒绝了输出中的一些行,因为它们无关紧要
因此,我们看到要导出的以下符号。
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp不导出任何内容,我们没有看到它的符号
链接我们的对象文件
$ g++ src1.o src2.o -o prog
并运行它
$ ./prog
123
Linker看到导出的符号并将其链接起来
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
并重建对象文件
$ g++ -c src2.cpp -o src2.o
好的(没有错误),因为我们只构建对象文件,链接还没有完成。尝试链接
$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
发生这种情况是因为我们的local_var_name是静态的,即它对其他模块不可见。现在更深入。获取翻译阶段输出
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp"
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
所以,我们看到local_var_name没有标签,这就是链接器找不到它的原因。但我们是黑客:),我们可以修复它。在文本编辑器中打开src1.s并更改
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
to
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
也就是说,你应该像下面这样
.file "src1.cpp"
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
.globl global_var_name
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; ...
我们已经更改了localvarname的可见性,并将其值设置为456789。尝试从中构建对象文件
$ g++ -c src1.s -o src2.o
好,请参阅readelf输出(符号)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
现在local_var_name具有绑定GLOBAL(以前是local)
link
$ g++ src1.o src2.o -o prog
并运行它
$ ./prog
123456789
好的,我们破解它:)
因此,当链接器在对象文件中找不到全局符号时,就会发生“未定义的引用/未解析的外部符号错误”。
未能链接到适当的库/对象文件或编译实现文件
通常,每个翻译单元都会生成一个包含该翻译单元中定义的符号定义的对象文件。要使用这些符号,必须链接这些对象文件。
在gcc下,您可以指定要在命令行中链接在一起的所有对象文件,或者一起编译实现文件。
g++ -o test objectFile1.o objectFile2.o -lLibraryName
-我。。。必须位于任何.o/.c/.cpp文件的右侧。
这里的libraryName只是库的裸名,没有特定于平台的添加。例如,在Linux上,库文件通常被称为libfoo.So,但您只能编写-lfo。在Windows上,相同的文件可能被称为foo.lib,但您将使用相同的参数。您可能需要添加目录,在该目录中可以使用-Lûdirectory›找到这些文件。确保不要在-l或-l后面写空格。
对于Xcode:添加用户标题搜索路径->添加库搜索路径->将实际的库引用拖放到项目文件夹中。
在MSVS下,添加到项目中的文件会自动将其对象文件链接在一起,并生成一个lib文件(常见用法)。要在单独的项目中使用符号,您需要需要在项目设置中包含lib文件。这是在项目财产的链接器部分的Input->Additional Dependencies中完成的。(指向lib文件的路径应为在Linker->General->Additional Library Directories中添加)当使用随lib文件提供的第三方库时,失败通常会导致错误。
还可能发生忘记将文件添加到编译中的情况,在这种情况下,不会生成对象文件。在gcc中,您可以将文件添加到命令行。在MSVS中,将文件添加到项目将使其自动编译(尽管文件可以手动从构建中单独排除)。
在Windows编程中,未链接必要库的标志是未解析符号的名称以__imp_开头。在文档中查找函数的名称,它应该指出您需要使用哪个库。例如,MSDN将信息放在名为“库”的部分中每个函数底部的框中。
在我的例子中,语法是正确的,但当一个类调用同一DLL中的第二个类时,我出现了错误。第二个类的CPP文件在visual studio中具有错误的财产->项类型,在我的例子中,它被设置为C/C++头,而不是正确的C/C++编译器,因此编译器在构建CPP文件时没有编译它,并导致错误LNK2019
下面是一个示例,假设语法正确,您应该通过更改财产中的项类型来获得错误
//class A header file
class ClassB; // FORWARD DECLERATION
class ClassA
{
public:
ClassB* bObj;
ClassA(HINSTANCE hDLL) ;
// member functions
}
--------------------------------
//class A cpp file
ClassA::ClassA(HINSTANCE hDLL)
{
bObj = new ClassB();// LNK2019 occures here
bObj ->somefunction();// LNK2019 occures here
}
/*************************/
//classB Header file
struct mystruct{}
class ClassB{
public:
ClassB();
mystruct somefunction();
}
------------------------------
//classB cpp file
/* This is the file with the wrong property item type in visual studio --C/C++ Header-*/
ClassB::somefunction(){}
ClassB::ClassB(){}
推荐文章
- 为什么我的程序不能在Windows 7下用法语编译?
- 如何获取变量的类型?
- 什么是奇怪的重复模板模式(CRTP)?
- 连接两个向量的最佳方法是什么?
- 在c++中,是通过值传递更好,还是通过引用到const传递更好?
- 在STL中deque到底是什么?
- Windows上最好的免费c++分析器是什么?
- 如何自动转换强类型枚举为int?
- 在一个类中使用具有成员函数的泛型std::function对象
- 'for'循环中的后增量和前增量产生相同的输出
- 虚函数和纯虚函数的区别
- c++中的_tmain()和main()有什么区别?
- 内存泄漏是否正常?
- 当启用c++ 11时,std::vector性能回归
- 什么时候使用哪种指针?