什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
编译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系统上也可以
其他回答
编译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系统上也可以
未能链接到适当的库/对象文件或编译实现文件
通常,每个翻译单元都会生成一个包含该翻译单元中定义的符号定义的对象文件。要使用这些符号,必须链接这些对象文件。
在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将信息放在名为“库”的部分中每个函数底部的框中。
因为当涉及到链接器错误时,人们似乎都在关注这个问题,所以我将在这里添加这个问题。
GCC 5.2.0中出现链接器错误的一个可能原因是现在默认选择了新的libstdc++库ABI。
如果您在std::__cxx11命名空间或标记[abi:cx11]中获得了有关符号未定义引用的链接器错误,则可能表明您正在尝试将使用_GLIBCXX_USE_cxx11_abi宏的不同值编译的对象文件链接在一起。这通常发生在链接到使用旧版本GCC编译的第三方库时。如果无法使用新的ABI重建第三方库,则需要使用旧的ABI重新编译代码。
因此,如果在5.1.0之后切换到GCC时突然出现链接器错误,这将是一件值得检查的事情。
链接在引用库的对象文件之前使用库
您正在尝试编译程序并将其与GCC工具链链接。链接指定了所有必要的库和库搜索路径如果libfoo依赖于libbar,那么链接会正确地将libfoo放在libbar之前。链接失败,对某些错误的引用未定义。但是所有未定义的东西都在你的头文件中声明#并且实际上是在您链接的库中定义的。
示例在C中。它们也可以是C++
一个涉及您自己构建的静态库的最小示例
my_lib.c文件
#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
puts("Hello World");
}
my_lib.h
#ifndef MY_LIB_H
#define MT_LIB_H
extern void hw(void);
#endif
例如1.c
#include <my_lib.h>
int main()
{
hw();
return 0;
}
构建静态库:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
编译程序:
$ gcc -I. -c -o eg1.o eg1.c
尝试将其与libmy_lib.a链接,但失败:
$ gcc -o eg1 -L. -lmy_lib eg1.o
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
如果您在一个步骤中编译和链接,则会得到相同的结果,例如:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
一个涉及共享系统库的最小示例,即压缩库libz
例如2.c
#include <zlib.h>
#include <stdio.h>
int main()
{
printf("%s\n",zlibVersion());
return 0;
}
编译程序:
$ gcc -c -o eg2.o eg2.c
尝试将程序与libz链接并失败:
$ gcc -o eg2 -lz eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
如果您一次编译并链接:
$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
示例2的一个变体涉及pkg配置:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
你做错了什么?
在要链接的对象文件和库序列中程序,您将库放在引用的对象文件之前他们您需要将库放在引用对他们来说。
正确链接示例1:
$ gcc -o eg1 eg1.o -L. -lmy_lib
成功:
$ ./eg1
Hello World
正确链接示例2:
$ gcc -o eg2 eg2.o -lz
成功:
$ ./eg2
1.2.8
正确链接示例2 pkg配置变体:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
解释
从这里开始,阅读是可选的。
默认情况下,GCC在您的发行版上生成的链接命令,在中从左到右使用链接中的文件命令行序列。当它发现一个文件引用了某个并且不包含其定义,to将搜索定义在更右边的文件中。如果它最终找到了定义解析引用。如果任何引用在结束时仍未解决,链接失败:链接器没有向后搜索。
首先,示例1,使用静态库my_lib.a
静态库是对象文件的索引存档。当链接器在链接序列中找到-lmy-lib,并指出这是指到静态库/libmy_lib.a,它想知道您的程序是否需要libmy_lib.a中的任何对象文件。
libmy_lib.a中只有一个对象文件,即my_lib.o,并且只定义了一个对象在my_lib.o中,即函数hw。
链接器将决定您的程序需要my_lib.o,如果并且仅当它已经知道您的程序在一个或多个对象文件中引用hw添加到程序中,并且没有添加任何对象文件包含hw的定义。
如果这是真的,那么链接器将从库中提取my_lib.o的副本,并将其添加到您的程序中。然后,您的程序包含hw的定义,因此其对hw的引用被解析。
当您尝试链接程序时,如:
$ gcc -o eg1 -L. -lmy_lib eg1.o
链接器在看到时没有将eg1.o添加到程序中-lmy_库。因为在这一点上,它还没有看到eg1.o。您的程序尚未引用hw:it根本没有做任何引用,因为它做的所有引用在eg1.o中。
因此,链接器不会将my_lib.o添加到程序中,并且没有其他内容用于libmy_lib.a。
接下来,它找到eg1.o,并将其添加到程序中。中的对象文件链接序列总是添加到程序中。现在,该程序使对hw的引用,并且不包含hw的定义;但是链接序列中没有任何内容可以提供缺失释义对hw的引用最终无法解析,链接失败。
第二,示例2,使用共享库libz
共享库不是对象文件或类似文件的存档更像是一个没有主功能的程序而是公开它定义的多个其他符号程序可以在运行时使用它们。
今天,许多Linux发行版配置其GCC工具链,以便其语言驱动程序(GCC、g++、gfortran等)指示系统链接器(ld)根据需要链接共享库。你有一个这样的发行版。
这意味着当链接器在链接序列中找到-lz,并发现这是指到共享库(例如)/usr/lib/x86_64-linux-gnu/libz,它想知道它添加到程序中的任何尚未定义的引用是否具有libz导出的定义
如果这是真的,那么链接器将不会从libz和将它们添加到您的程序中;相反,它只会修改程序的代码从而:-
在运行时,系统程序加载器会将libz的副本加载到无论何时加载程序的副本,都可以执行与程序相同的过程。在运行时,每当程序引用libz,该引用使用中libz副本导出的定义相同的过程。
您的程序只想引用一个由libz导出的定义,即函数zlibVersion,在eg2.c中仅引用一次。如果链接器将该引用添加到程序中,然后找到定义由libz导出,引用被解析
但当您尝试链接程序时,如:
gcc -o eg2 -lz eg2.o
事件的顺序是错误的,其方式与示例1相同。在链接器找到-lz时,没有对任何内容的引用在节目中:他们都在eg2.o中,这还没有被看到。所以链接器决定它不适用于libz。当它达到eg2.o时,将其添加到程序中,然后对zlibVersion有未定义的引用,链接序列完成;该引用未解析,链接失败。
最后,示例2的pkg配置变体现在有了一个显而易见的解释。壳体膨胀后:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
变为:
gcc -o eg2 -lz eg2.o
这再次只是示例2。
我可以重复示例1中的问题,但不能重复示例2中的问题
联动装置:
gcc -o eg2 -lz eg2.o
对你来说很好!
(或者:在Fedora 23上,这种链接很好,但在Ubuntu 16.04上失败了)
这是因为链接工作的发行版是不配置其GCC工具链以根据需要链接共享库。
过去,类unix系统链接静态和共享是很正常的不同的规则。链接序列中的静态库已链接但是共享库是无条件链接的。
这种行为在链接时是经济的,因为链接器不必考虑程序是否需要共享库:如果是共享库,大多数链接中的大多数库都是共享库。但也有缺点:-
这在运行时是不经济的,因为它会导致共享库与程序一起加载,即使不需要它们。静态库和共享库的不同链接规则可能会令人困惑对于不熟练的程序员,他们可能不知道-lfo是否在他们的联系中将解析为/some/where/libfoo.a或/some/wwhere/libfoo.so,并且可能不理解共享库和静态库之间的区别无论如何
这种权衡导致了今天的分裂局面。一些发行版有更改了共享库的GCC链接规则,以便根据需要这一原则适用于所有图书馆。一些发行版沿用了旧版本方法
为什么即使同时编译和链接,我仍然会遇到这个问题?
如果我这样做:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
当然,gcc必须首先编译eg1.c,然后链接生成的带有libmy_lib.a的对象文件,那么它怎么可能不知道该对象文件在进行链接时是否需要?
因为使用单个命令编译和链接不会更改连杆顺序。
当您运行上面的命令时,gcc发现您需要编译+链接。因此,在幕后,它生成一个编译命令,并运行然后生成一个链接命令并运行它,就像您运行了两个命令:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
因此,如果运行这两个命令,链接就会失败。这个您在失败中注意到的唯一区别是gcc生成了编译+链接情况下的临时对象文件,因为您没有告诉它使用eg1.o.我们看到:
/tmp/ccQk1tvs.o: In function `main'
而不是:
eg1.o: In function `main':
另请参见
指定相互依赖的链接库的顺序错误
将相互依赖的库按错误的顺序排列只是一种方法在其中,您可以获取需要对即将到来的事物进行定义的文件在链接中晚于提供定义的文件。将库放在引用它们的对象文件是犯同样错误的另一种方式。
需要考虑的一些拼写错误:(我作为初学者经常遇到)
如果您使用的是类:请检查您是否没有在定义函数的cpp文件中的函数名之前忘记“classname::”。如果使用forward声明:请确保声明正确的类型。例如:如果要转发声明“结构”,请使用“结构”而不是“类”。