什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?


当前回答

当包含路径不同时

当头文件及其关联的共享库(.lib文件)不同步时,可能会发生链接器错误。让我解释一下。

链接器是如何工作的?链接器通过比较函数声明(在头中声明)和函数定义(在共享库中)的签名来匹配它们。如果链接器找不到完全匹配的函数定义,则可能会出现链接器错误。

即使声明和定义似乎匹配,仍然可能出现链接器错误吗?对它们在源代码中看起来可能相同,但这实际上取决于编译器所看到的内容。基本上,你可能会遇到这样的情况:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

注意,即使两个函数声明在源代码中看起来相同,但根据编译器的不同,它们确实不同。

你可能会问,一个人在这样的情况下是如何结束的?当然包括路径!如果在编译共享库时,include路径指向header1.h,而您最终在自己的程序中使用了header2.h,那么您将无法理解发生了什么(双关语)。

下面将解释这在现实世界中如何发生的一个示例。

通过示例进一步阐述

我有两个项目:graphics.lib和main.exe。两个项目都依赖common_math.h。假设库导出以下函数:

// graphics.lib    
#include "common_math.h" 
   
void draw(vec3 p) { ... } // vec3 comes from common_math.h

然后,您继续将库包含在您自己的项目中。

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

繁荣你得到了一个链接器错误,你不知道它为什么会失败。原因是公共库使用相同includecommon_math.h的不同版本(我在本例中通过包含不同的路径来说明这一点,但可能并不总是那么明显。可能编译器设置中的包含路径不同)。

注意,在这个例子中,链接器会告诉你它找不到draw(),而实际上你知道它显然是由库导出的。你可以花几个小时挠头,想知道出了什么问题。问题是,链接器看到的签名不同,因为参数类型略有不同。在本例中,就编译器而言,vec3在两个项目中都是不同的类型。这可能是因为它们来自两个稍微不同的包含文件(可能包含文件来自库的两个不同版本)。

调试链接器

如果您正在使用Visual Studio,DUMPBIN是您的朋友。我相信其他编译器也有类似的工具。

过程如下:

注意链接器错误中给出的奇怪的损坏名称。(例如。draw@graphics@XYZ)。将库中导出的符号转储到文本文件中。搜索导出的感兴趣符号,并注意损坏的名称不同。请注意,为什么被弄乱的名字最终会不同。您将能够看到参数类型不同,即使它们在源代码中看起来相同。它们不同的原因。在上面给出的示例中,它们是不同的,因为包含文件不同。

[1] 我所说的项目是指一组链接在一起以生成库或可执行文件的源文件。

编辑1:改写第一节,使其更容易理解。请在下面评论,让我知道是否需要修复其他问题。谢谢

其他回答

编译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系统上也可以

链接的.lib文件与.dll关联

我也有同样的问题。假设我有项目MyProject和TestProject。我已经有效地将MyProject的lib文件链接到TestProject。然而,此lib文件是在构建MyProject的DLL时生成的。此外,我没有包含MyProject中所有方法的源代码,只包含对DLL入口点的访问。

为了解决这个问题,我将MyProject构建为LIB,并将TestProject链接到此.LIB文件(我将生成的.LIB文件复制粘贴到TestProject文件夹中)。然后我可以再次将MyProject构建为DLL。它正在编译,因为TestProject链接到的库确实包含MyProject中类中所有方法的代码。

在我的例子中,语法是正确的,但当一个类调用同一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(){}

未能链接到适当的库/对象文件或编译实现文件

通常,每个翻译单元都会生成一个包含该翻译单元中定义的符号定义的对象文件。要使用这些符号,必须链接这些对象文件。

在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将信息放在名为“库”的部分中每个函数底部的框中。

我正在构建一个共享/动态库。它在Linux和*BSD上运行,但在Mac OS X上,完全相同的编译和链接命令会产生未解决的引用错误。有什么好处?

Mac OS X在内部与Linux和*BSD非常不同。对象/可执行文件格式为

在Linux和*BSD上,当构建共享库时,默认情况下允许未解析的引用。期望它们在加载时能够满足主可执行文件和/或其他共享库的要求。如果在加载时无法解析这些符号,则共享库将无法加载。

在Mac OS X上,构建动态库时,默认情况下不允许未解析的引用。如果希望在加载时解析引用,则需要显式启用未解析的引用。这是使用未定义的dynamic_lookup链接器标志完成的。

在构建可加载插件时,允许未解析的引用非常有用。