什么是未定义的引用/未解决的外部符号错误以及如何修复它?

什么是未定义的引用/未解决的外部符号错误?什么是常见原因,如何修复/防止它们?

849168 次浏览

编译C++程序需要几个步骤,如2.2(由Keith Thompson提供参考)所述:

翻译语法规则之间的优先级由以下阶段[见脚注]指定。

  1. 物理源文件字符以实现定义的方式映射到基本源字符集(为行尾指示器引入新行字符)如果[SNIP]
  2. 删除紧跟新行字符的反斜杠字符(\)的每个实例,将物理源行拼接到形成逻辑源线。[SNIP]
  3. 源文件被分解为预处理标记(2.5)和空白字符序列(包括注释)。[SNIP]
  4. 预处理指令被执行,宏调用被展开,_Pragma一元操作符表达式被执行。
  5. 字符文字或字符串文字中的每个源字符集成员,以及每个转义序列和通用字符名称在字符文字或非原始字符串文字中,转换为执行字符集的对应成员;[SNIP]
  6. 相邻的字符串文字标记被连接。
  7. 分隔标记的空白字符不再有效。每个预处理标记都转换为一个标记。(2.7)。该生成的标记在语法和语义上进行分析和翻译为翻译单元。[SNIP]
  8. 翻译的翻译单元和实例化单元组合如下:[SNIP]
  9. 所有外部实体引用都被解析。链接库组件以满足对未在所有这样的翻译输出被收集到一个程序映像,其中包含在其内部执行所需的信息执行环境。(强调我的)

[脚注]实现必须表现得好像这些单独的阶段发生了,尽管在实践中不同的阶段可能会折叠在一起。

指定的错误发生在编译的最后阶段,通常称为链接。它基本上意味着您将一堆实现文件编译为目标文件或库,现在您希望它们一起工作。

假设您在a.cpp中定义了符号a。现在,b.cpp宣布该符号并使用了它。在链接之前,它只是假设该符号被定义为某处,但它还不关心在哪里。链接阶段负责查找符号并将其正确链接到b.cpp(实际上是使用它的对象或库)。

如果您使用的是Microsoft Visual Studio,您将看到项目生成.lib文件。这些文件包含一个导出符号表和一个导入符号表。导入的符号将根据您链接到的库进行解析,并为使用该.lib(如果有)的库提供导出的符号。

其他编译器/平台也存在类似的机制。

常见的错误消息是微软Visual Studioerror LNK2001error LNK1120error LNK2019海湾合作委员会undefined reference to符号名称

代码:

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;}

将使用海湾合作委员会生成以下错误:

/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

微软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

常见原因包括:

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

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

gcc下,您将指定要在命令行中链接在一起的所有对象文件,或者一起编译实现文件。

g++ -o test objectFile1.o objectFile2.o -lLibraryName

-l...必须位于任何.o/.c/.cpp文件的右侧。

这里的libraryName只是库的名称,没有特定于平台的添加。例如,在Linux库文件通常称为libfoo.so,但您只写-lfoo。在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将信息放在名为“Library”的部分中每个函数底部的框中。

已声明但未定义变量或函数。

一个典型的变量声明是

extern int x;

由于这只是一个声明,因此需要单一定义。相应的定义是:

int x;

例如,以下操作将生成错误:

extern int x;int main(){x = 0;}//int x; // uncomment this line for successful definition

类似的注释适用于函数。声明函数而不定义它会导致错误:

void foo(); // declaration onlyint main(){foo();}//void foo() {} //uncomment this line for successful definition

请注意,您实现的函数与您声明的函数完全匹配。例如,您可能有不匹配的cv限定符:

void foo(int& x);int main(){int x;foo(x);}void foo(const int& x) {} //different function, doesn't provide a definition//for void foo(int& x)                          

错配的其他例子包括

  • 函数/变量在一个命名空间中声明,在另一个命名空间中定义。
  • 函数/变量声明为类成员,定义为全局(反之亦然)。
  • 函数返回类型、参数编号和类型以及调用约定并不完全一致。

来自编译器的错误消息通常会为您提供已声明但从未定义的变量或函数的完整声明。将其与您提供的定义密切比较。确保每个细节都匹配。

类成员:

virtual析构函数需要一个实现。

声明析构函数纯仍然需要您定义它(与常规函数不同):

struct X{virtual ~X() = 0;};struct Y : X{~Y() {}};int main(){Y y;}//X::~X(){} //uncomment this line for successful definition

这是因为基类析构函数在对象被隐式销毁时被调用,所以需要一个定义。

virtual方法必须实现或定义为纯方法。

这类似于没有定义的非virtual方法,增加了以下推理纯声明生成一个虚拟的vtable,你可能会在不使用函数的情况下得到链接器错误:

struct X{virtual void foo();};struct Y : X{void foo() {}};int main(){Y y; //linker error although there was no call to X::foo}

为此,将X::foo()声明为纯:

struct X{virtual void foo() = 0;};

virtual类成员

一些成员需要定义,即使没有显式使用:

struct A{~A();};

以下将产生错误:

A a;      //destructor undefined

实现可以是内联的,在类定义本身中:

struct A{~A() {}};

或外部:

A::~A() {}

如果实现在类定义之外,但在标头中,则必须将方法标记为inline以防止多重定义。

如果使用,则需要定义所有使用的成员方法。

一个常见的错误是忘记限定名称:

struct A{void foo();};
void foo() {}
int main(){A a;a.foo();}

定义应为

void A::foo() {}

static数据成员必须在单翻译单元中的类之外定义:

struct X{static int x;};int main(){int x = X::x;}//int X::x; //uncomment this line to define X::x

可以为类定义中整数或枚举类型的staticconst数据成员提供初始化器;但是,该成员的odr使用仍然需要如上所述的命名空间范围定义。C++11允许在类内初始化所有static const数据成员。

模板实现不可见。

非专用模板的定义必须对所有使用它们的翻译单元可见。这意味着您不能分离模板的定义如果您必须分离实现,通常的解决方法是在标题末尾包含一个impl文件声明模板。常见的情况是:

template<class T>struct X{void foo();};
int main(){X<int> x;x.foo();}
//differentImplementationFile.cpptemplate<class T>void X<T>::foo(){}

要解决这个问题,您必须将X::foo的定义移动到头文件或使用它的翻译单元可见的某个位置。

专门化模板可以在实现文件中实现,实现不必是可见的,但必须事先声明专门化。

有关进一步的解释和另一种可能的解决方案(显式实例化),请参阅这个问题和答案

符号在C程序中定义并用于C++代码。

函数(或变量)void foo()是在C程序中定义的,您尝试在C++程序中使用它:

void foo();int main(){foo();}

C++链接器期望名称被破坏,因此您必须将函数声明为:

extern "C" void foo();int main(){foo();}

同样,函数(或变量)void foo()不是在C程序中定义的,而是在C++中定义的,但使用C链接:

extern "C" void foo();

您尝试在具有C++链接的C++程序中使用它。

如果整个库包含在头文件中(并且被编译为C代码);包含需要如下;

extern "C" {#include "cheader.h"}

跨模块/dll错误地导入/导出方法/类(特定于编译器)。

MSVS要求您使用__declspec(dllexport)__declspec(dllimport)指定要导出和导入的符号。

这种双重功能通常通过使用宏来获得:

#ifdef THIS_MODULE#define DLLIMPEXP __declspec(dllexport)#else#define DLLIMPEXP __declspec(dllimport)#endif

THIS_MODULE只会在导出函数的模块中定义。这样,声明:

DLLIMPEXP void foo();

扩展到

__declspec(dllexport) void foo();

并告诉编译器导出函数,因为当前模块包含其定义。当将声明包含在不同的模块中时,它将扩展为

__declspec(dllimport) void foo();

并告诉编译器该定义位于您链接到的库之一中(另请参阅1))。

您可以使用类似的导入/导出类:

class DLLIMPEXP X{};

如果一切都失败了,重新编译。

我最近能够通过重新编译违规文件来摆脱Visual Studio 2012中未解决的外部错误。当我重新构建时,错误消失了。

这通常发生在两个(或多个)库具有循环依赖关系时。库A尝试使用B.lib中的符号,库B尝试使用A.lib.中的符号。当您尝试编译A时,链接步骤将失败,因为它找不到B.lib.A.lib将生成,但没有dll。然后您编译B,这将成功并生成B.lib.重新编译A现在将工作,因为现在找到B.lib。

未定义的引用#0或类似的'不寻常'#1切入点引用(尤其是)。

您可能错过了使用实际IDE选择正确的项目类型。IDE可能希望将例如Windows应用程序项目绑定到此类切入点函数(如上面缺少的引用中指定的),而不是常用的int main(int argc, char** argv);签名。

如果您的IDE支持Plain控制台项目,您可能希望选择此项目类型,而不是Windows应用程序项目。


下面是从现实世界问题中更详细地处理的case1case2

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

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

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

此外,如果您使用的是第三方库,请确保您拥有正确的32/64位二进制文件

指定相互依赖的链接库的顺序是错误的。

如果库相互依赖,则链接库的顺序很重要。一般来说,如果库A依赖于库B,那么libA必须在链接器标志中出现在libB之前。

例如:

// B.h#ifndef B_H#define B_H
struct B {B(int);int x;};
#endif
// B.cpp#include "B.h"B::B(int xx) : x(xx) {}
// A.h#include "B.h"
struct A {A(int x);B b;};
// A.cpp#include "A.h"
A::A(int x) : b(x) {}
// main.cpp#include "A.h"
int main() {A a(5);return 0;};

创建库:

$ g++ -c A.cpp$ g++ -c B.cpp$ ar rvs libA.a A.oar: creating libA.aa - A.o$ ar rvs libB.a B.oar: creating libB.aa - B.o

编译:

$ g++ main.cpp -L. -lB -lA./libA.a(A.o): In function `A::A(int)':A.cpp:(.text+0x1c): undefined reference to `B::B(int)'collect2: error: ld returned 1 exit status$ g++ main.cpp -L. -lA -lB$ ./a.out

所以再重复一遍,顺序确实很重要!

Microsoft提供了#pragma以在链接时引用正确的库;

#pragma comment(lib, "libname.lib")

除了包含库目录的库路径外,这应该是库的全名。

什么是“未定义的引用/未解析的外部符号”

我将尝试解释什么是“未定义的引用/未解析的外部符号”。

注意:我使用g++和Linux,所有示例都适用于它

例如,我们有一些代码

// src1.cppvoid print();
static int local_var_name; // 'static' makes variable not visible for other modulesint global_var_name = 123;
int main(){print();return 0;}

// src2.cppextern "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.oNum:    Value          Size Type    Bind   Vis      Ndx Name5: 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

并运行它

$ ./prog123

链接器看到导出的符号并链接它。现在我们尝试取消注释行在src2.cpp像这样

// src2.cppextern "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

OK(没有错误),因为我们只构建对象文件,链接还没有完成。点击链接

$ g++ src1.o src2.o -o progsrc2.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.slook 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, 4global_var_name:.long   123.text.globl  main.type   main, @functionmain:; 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

    .globl  local_var_name.data.align 4.type   local_var_name, @object.size   local_var_name, 4local_var_name:.long   456789

你应该像下面这样

    .file   "src1.cpp".globl  local_var_name.data.align 4.type   local_var_name, @object.size   local_var_name, 4local_var_name:.long   456789.globl  global_var_name.align 4.type   global_var_name, @object.size   global_var_name, 4global_var_name:.long   123.text.globl  main.type   main, @functionmain:; ...

我们更改了local_var_name的可见性并将其值设置为456789。尝试从它构建一个目标文件

$ g++ -c src1.s -o src2.o

好的,请参阅自述输出(符号)

$ readelf --symbols src1.o8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

现在local_var_name绑定全球(是本地)

链接

$ g++ src1.o src2.o -o prog

并运行它

$ ./prog123456789

好的,我们破解它:)

因此,当链接器无法在目标文件中找到全局符号时,就会发生“未定义的引用/未解决的外部符号错误”。

编译器/IDE中的bug

我最近遇到了这个问题,结果是这是Visual Studio Express 2013中的bug。我不得不从项目中删除一个源文件并重新添加它以克服bug。

如果您认为它可能是编译器/IDE中的bug,请尝试以下步骤:

  • 清理项目(一些IDE可以选择这样做,你也可以通过删除对象文件手动执行)
  • 尝试开始一个新项目,从原始代码复制所有源代码。

Visual Studio NuGet包需要更新为新工具集版本

我只是在尝试将libpng与Visual Studio 2013链接时遇到了这个问题。问题是包文件只有Visual Studio 2010和2012的库。

正确的解决方案是希望开发人员发布一个更新的包,然后升级,但它对我来说是通过黑客攻击VS2013的额外设置,指向VS2012库文件。

我通过找到packagename\build\native\packagename.targets并在该文件中复制所有v110部分来编辑包(在解决方案目录内的packages文件夹中)。我将v110更改为条件字段只中的v120,非常小心地将文件名路径全部保留为v110。这只是允许Visual Studio 2013链接到2012年的库,在这种情况下,它起作用了。

不支持链接器脚本的GNU ld包装器

一些. so文件实际上是GNU ld链接器脚本,例如libtbb.so文件是一个ASCII文本文件,内容如下:

INPUT (libtbb.so.2)

一些更复杂的构建可能不支持这一点。例如,如果您在编译器选项中包含-v,您可以看到Mainwin gcc包装器mwdip丢弃要链接的库的详细输出列表中的链接器脚本命令文件。一个简单的解决方法是用文件的副本(或符号链接)替换链接器脚本输入命令文件,例如:

cp libtbb.so.2 libtbb.so

或者您可以将-l参数替换为. so的完整路径,例如,而不是-ltbb do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

这是每个VC++程序员一次又一次看到的最令人困惑的错误消息之一。让我们先把事情弄清楚。

一、什么是符号?简而言之,符号就是一个名称。它可以是变量名、函数名、类名、typedef名,或者除了属于C++语言的名称和符号之外的任何名称和符号。它是用户定义的或由依赖库(另一个用户定义的)引入的。

二、什么是外在?在VC++中,每个源文件(. cpp,. c等)都被视为一个翻译单元,编译器一次编译一个单元,并为当前翻译单元生成一个目标文件(. obj)。(请注意,此源文件包含的每个头文件都将被预处理,并将被视为此翻译单元的一部分)翻译单元内的所有内容都被视为内部,其他所有内容都被视为外部。在C++中,您可以使用extern__declspec (dllimport)等关键字引用外部符号。

C.什么是“决心”?Resolve是一个链接时术语。在链接时中,链接器尝试为目标文件中无法在内部找到其定义的每个符号查找外部定义。此搜索过程的范围包括:

  • 编译时生成的所有目标文件
  • 所有显式或隐式的库(. lib)指定为此构建应用程序的附加依赖项。

这个搜索过程称为解析。

最后,为什么未解决的外部符号?如果链接器找不到内部没有定义的符号的外部定义,它会报告未解决的外部符号错误。

E. LNK2019的可能原因:未解决的外部符号错误。我们已经知道这个错误是由于链接器未能找到外部符号的定义,可能的原因可以排序为:

  1. 定义存在

例如,如果我们在a.cpp中定义了一个名为foo的函数:

int foo(){return 0;}

在b.cpp我们想要调用函数foo,所以我们添加

void foo();

要声明函数foo()并在另一个函数体中调用它,比如bar()

void bar(){foo();}

现在,当你构建这段代码时,你会收到一个LNK2019错误,抱怨foo是一个未解析的符号。在这种情况下,我们知道foo()在a.cpp中有它的定义,但与我们调用的不同(不同的返回值)。这就是定义存在的情况。

  1. 定义不存在

如果我们想调用库中的某些函数,但导入库未添加到项目设置的附加依赖列表(从:Project | Properties | Configuration Properties | Linker | Input | Additional Dependency设置)中。现在链接器将报告LNK2019,因为当前搜索范围中不存在该定义。

使用链接器帮助诊断错误

大多数现代链接器都包含一个详细的选项,可以在不同程度上打印出来;

  • 链接调用(命令行),
  • 关于链接阶段包含哪些库的数据,
  • 图书馆的位置,
  • 使用的搜索路径。

对于gcc和clang;您通常会将-v -Wl,--verbose-v -Wl,-v添加到命令行。更多详细信息可以在这里找到;

对于MSVC,/VERBOSE(特别是/VERBOSE:LIB)被添加到链接命令行。

假设你有一个用c++编写的大项目,其中包含一千个. cpp文件和一千个. h文件。

编译的意义是什么?

  • Visual Studio搜索文件. vcxproj并开始编译扩展名为. cpp的每个文件。编译顺序未定义。所以你不能假设文件main.cpp首先被编译
  • 如果. cpp文件依赖于其他. h文件来查找符号可以在文件. cpp
  • 中定义也可以不定义
  • 如果存在一个编译器找不到一个符号的. cpp文件,则编译时间错误会引发消息符号x找不到
  • 对于扩展名为. cpp的每个文件,都会生成一个对象文件. o,Visual Studio还会将输出写入名为ProjectName.Cpp.Clean.txt的文件中,该文件包含链接器必须处理的所有对象文件。

Linker应该合并所有目标文件,最终构建输出(可能是可执行文件或库)

链接项目的步骤

  • 解析所有目标文件并找到仅在头文件中声明的定义(例如:前面答案中提到的类的一个方法的代码,或事件初始化属于类内成员的静态变量)
  • 如果在目标文件中找不到一个符号,他也会在附加库中搜索。对于将新库添加到项目配置属性->VC++目录->图书馆目录,在这里您指定了用于搜索库的附加文件夹,并指定了配置属性->链接器->输入用于指定库的名称。-如果链接器找不到你在一个. cpp中写的符号,他会提出一个连接器时间误差,听起来像error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

观察

  1. 一旦链接器找到一个符号,他就不会在其他库中搜索它
  2. 链接库的顺序确实重要
  3. 如果Linker在一个静态库中找到外部符号,他会将该符号包含在项目的输出中。但是,如果库是共享的(动态),他不会将代码(符号)包含在输出中,但可能会发生运行时间崩溃

如何解决这种错误

编译器时间错误:

  • 确保你写的c++项目语法正确。

链接器时间错误

  • 定义您在头文件中声明的所有符号
  • 使用#pragma once允许编译器不包含一个标头,如果它已经包含在当前编译的. cpp中
  • 确保您的外部库不包含可能与您在头文件中定义的其他符号发生冲突的符号
  • 当您使用模板确保在头文件中包含每个模板函数的定义以允许编译器为任何实例化生成适当的代码时。

由于当涉及到链接器错误时,人们似乎会被引导到这个问题,我将在这里添加这个。

GCC 5.2.0出现链接器错误的一个可能原因是现在默认选择了新的libstdc++库ABI。

如果您收到有关对涉及std::__cxx11命名空间或标记[abi: cxx11]中类型的符号的未定义引用的链接器错误,那么它可能表明您正在尝试将使用_GLIBCXX_USE_CXX11_ABI宏的不同值编译的目标文件链接在一起。这通常发生在链接到使用旧版本GCC编译的第三方库时。如果第三方库无法使用新ABI重建,那么您将需要使用旧ABI重新编译代码。

因此,如果您在5.1.0之后切换到GCC时突然出现链接器错误,这将是一件值得检查的事情。

清理和重建

构建的“干净”可以删除以前构建、失败构建、不完整构建和其他与构建系统相关的构建问题可能留下的“死木”。

一般来说,IDE或构建将包含某种形式的“干净”功能,但这可能没有正确配置(例如在手动makefile中)或可能失败(例如中间或生成的二进制文件是只读的)。

“清理”完成后,验证“清理”是否成功,并且所有生成的中间文件(例如自动makefile)都已成功删除。

过程可以被看作是最后的手段,但往往是良好的第一步;特别是如果最近添加了与错误相关的代码(本地或来自源存储库)。

朋友模板…

给定带有友元运算符(或函数)的模板类型的代码片段;

template <typename T>class Foo {friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);};

operator<<被声明为非模板函数。对于Foo使用的每个类型T,都需要有一个非模板化的operator<<。例如,如果有一个类型Foo<int>声明,那么必须有一个运算符实现,如下所示;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

由于未实现,链接器无法找到它并导致错误。

要纠正这一点,您可以在Foo类型之前声明一个模板运算符,然后声明为朋友,适当的实例化。语法有点尴尬,但看起来如下;

// forward declare the Footemplate <typename>class Foo;
// forward declare the operator <<template <typename T>std::ostream& operator<<(std::ostream&, const Foo<T>&);
template <typename T>class Foo {friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);// note the required <>        ^^^^// ...};
template <typename T>std::ostream& operator<<(std::ostream&, const Foo<T>&){// ... implement the operator}

上面的代码将操作员的友谊限制在Foo的相应实例化中,即operator<< <int>实例化仅限于访问Foo<int>实例化的私有成员。

替代品包括;

  • 允许友谊扩展到模板的所有实例化,如下所示;

    template <typename T>class Foo {template <typename T1>friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);// ...};
  • Or, the implementation for the operator<< can be done inline inside the class definition;

    template <typename T>class Foo {friend std::ostream& operator<<(std::ostream& os, const Foo& a){ /*...*/ }// ...};

Note, when the declaration of the operator (or function) only appears in the class, the name is not available for "normal" lookup, only for argument dependent lookup, from cppreference;

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not accessible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided...

There is further reading on template friends at cppreference and the C++ FAQ.

Code listing showing the techniques above.


As a side note to the failing code sample; g++ warns about this as follows

warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)

不一致的UNICODE定义

Windows UNICODE构建时,TCHAR等被定义为wchar_t等。当不构建时,UNICODE定义为构建,TCHAR定义为char等。这些UNICODE_UNICODE定义影响所有wchar_t0;LPTSTRLPCTSTR和它们的麋鹿。

构建一个定义了UNICODE的库并尝试将其链接到未定义UNICODE的项目中将导致链接器错误,因为TCHARcharwchar_t的定义不匹配。

该错误通常包括一个具有charwchar_t派生类型的值的函数,也可能包括std::basic_string<>等。在代码中浏览受影响的函数时,通常会引用TCHARstd::basic_string<TCHAR>等。这表明该代码最初是为UNICODE和多字节字符(或“窄”)构建而设计的。

要纠正这一点,请使用一致的UNICODE(和_UNICODE)定义构建所有必需的库和项目。

  1. 这两种方法都可以实现;

    #define UNICODE#define _UNICODE
  2. Or in the project settings;

    Project Properties > General > Project Defaults > Character Set

  3. Or on the command line;

    /DUNICODE /D_UNICODE

The alternative is applicable as well, if UNICODE is not intended to be used, make sure the defines are not set, and/or the multi-character setting is used in the projects and consistently applied.

Do not forget to be consistent between the "Release" and "Debug" builds as well.

当您的包含路径不同时

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

链接器是如何工作的?链接器通过比较函数声明(在标头中声明)及其定义(在共享库中)来匹配它们的签名。如果链接器没有找到完全匹配的函数定义,你可能会得到链接器错误。

即使声明和定义看起来匹配,是否仍然有可能得到链接器错误?是的!它们在源代码中可能看起来相同,但这实际上取决于编译器看到的。本质上,你可能会遇到这样的情况:

// header1.htypedef int Number;void foo(Number);
// header2.htypedef float Number;void foo(Number); // this only looks the same lexically

请注意,尽管两个函数声明在源代码中看起来相同,但根据编译器的不同,它们实际上是不同的。

你可能会问为什么会出现这样的情况?包括路径当然!如果在编译共享库时,包含路径指向header1.h,而您最终在自己的程序中使用header2.h,那么您将不得不挠头想知道发生了什么(双关语)。

下面是在现实世界中如何发生这种情况的一个例子。

用一个例子进一步阐述

我有两个项目:graphics.libmain.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(...);}

Boom!你得到一个链接器错误,但是你不知道为什么它会失败。原因是公共库使用同一包含common_math.h的不同版本(我在示例中通过包含不同的路径使它变得明显,但它可能并不总是那么明显。也许包含路径在编译器设置中不同)。

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

调试链接器

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

过程是这样的:

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

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

编辑1:重写了第一部分,以便更容易理解。请在下面评论,让我知道是否还有其他需要修复的地方。谢谢!

您的链接在引用它们的目标文件之前消耗库

  • 您正在尝试编译您的程序并将其与GCC工具链链接。
  • 您的链接指定了所有必要的库和库搜索路径
  • 如果libfoo依赖于libbar,那么您的链接正确地将libfoo放在libbar之前。
  • 您的链接失败并出现undefined reference to东西错误。
  • 但是所有未定义的东西都在您拥有的头文件中声明#included,实际上是在您链接的库中定义的。

例子在C中,它们同样可以C++

一个涉及您自己构建的静态库的最小示例

my_libc

#include "my_lib.h"#include <stdio.h>
void hw(void){puts("Hello World");}

my_libH

#ifndef MY_LIB_H#define MT_LIB_H
extern void hw(void);
#endif

eg1. 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.oeg1.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

eg2. 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.oeg2.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-config

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.oeg2.o: In function `main':eg2.c:(.text+0x5): undefined reference to `zlibVersion'

你做错了什么?

在目标文件和库的序列中,您要链接以使您的程序,您正在将库放置在引用的目标文件之前它们。您需要将库之后放置在引用的对象文件中对他们来说

正确链接示例1:

$ gcc -o eg1 eg1.o -L. -lmy_lib

成功案例:

$ ./eg1Hello World

正确链接示例2:

$ gcc -o eg2 eg2.o -lz

成功案例:

$ ./eg21.2.8

正确链接示例2pkg-config变体:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)$ ./eg21.2.8

的解释

阅读是可选的从这里开始

默认情况下,GCC在您的发行版上生成的链接命令,从左到右使用链接中的文件命令行序列。当它发现文件引用东西时并且不包含它的定义,将搜索定义在更右边的文件中。如果它最终找到定义,则引用已解析。如果任何引用在末尾仍未解析,链接失败:链接器不会向后搜索。

首先,例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_lib。因为在那一点上,它还没有看到eg1.o。您的程序还没有对hw: it进行任何引用还没有做任何引用在所有,因为它做的所有引用eg1.o.

因此链接器没有将my_lib.o添加到程序中,并且没有进一步的使用libmy_lib.a

接下来,它找到eg1.o,并将其添加为程序。链接序列总是添加到程序中。现在,程序使对hw的引用,不包含hw的定义;但是链接序列中没有任何东西可以提供丢失的定义。对hw的引用以未解决结束,链接失败。

第二,例2,共享库libz

共享库不是目标文件或类似文件的存档。它是更像是没有main函数的程序相反,公开它定义的多个其他符号,以便其他程序可以在运行时使用它们。

今天许多Linux发行版配置他们的GCC工具链,以便其语言驱动程序(gccg++gfortran等)指示系统链接器(ld)以按需为基础链接共享库。你已经得到了其中一个发行版。

这意味着当链接器在链接序列中找到-lz时,并计算出这是指对于共享库(例如)/usr/lib/x86_64-linux-gnu/libz.so,它想知道它添加到程序中的任何尚未定义的引用是否具有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-config变体现在有一个明显的解释。shell扩展后:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

变成:

gcc -o eg2 -lz eg2.o

这是第二个例子。

我可以重现示例1中的问题,但不能重现示例2中的问题

联系:

gcc -o eg2 -lz eg2.o

为你工作很好!

(或者:这种链接在Fedora 23上运行良好,但在Ubuntu 16.04上失败了)

这是因为链接工作的发行版是没有将其GCC工具链配置为链接共享库按需

回到过去,类unix系统链接静态和共享是很正常的不同规则的库。链接序列中的静态库被链接在示例1中解释的按需基础上,但共享库是无条件链接的。

这种行为在链接时是经济的,因为链接器不必考虑程序是否需要共享库:如果是共享库,链接它。大多数链接中的大多数库都是共享库。但也有缺点:-

  • 运行时是不经济的,因为它会导致共享库与程序一起加载,即使不需要它们。

  • 静态库和共享库的不同链接规则可能会令人困惑给不熟练的程序员,他们可能不知道-lfoo是否在他们的链接中将解析为/some/where/libfoo.a/some/where/libfoo.so,并且可能不理解共享库和静态库之间的区别无论如何。

这种权衡导致了今天的分裂局面更改了共享库的GCC链接规则,以便按需原则适用于所有库。一些发行版坚持使用旧的方式。

为什么即使我同时编译和链接,我仍然会遇到这个问题?

如果我只做:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

当然gcc必须先编译eg1.c,然后链接结果对象文件libmy_lib.a。那么它怎么可能不知道该对象文件#36825;的时候,需要什么?

因为使用单个命令编译和链接不会改变链接序列的顺序。

当你运行上面的命令时,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':

另见

指定相互依赖的链接库的顺序是错误的

将相互依赖的库按错误的顺序放置只是一种方法其中您可以获取需要定义即将到来的事物的文件提供定义的文件之后的链接中。将库放在引用它们的对象文件是犯同样错误的另一种方式。

const变量声明/定义中缺少“extern”(仅C++)

对于来自C的人来说,在C++全局const变量具有内部(或静态)链接可能会令人惊讶。在C中并非如此,因为所有全局变量都是隐式的extern(即当static关键字缺失时)。

示例:

// file1.cppconst int test = 5;    // in C++ same as "static const int test = 5"int test2 = 5;
// file2.cppextern const int test;extern int test2;
void foo(){int x = test;   // linker error in C++ , no error in Cint y = test2;  // no problem}

正确的做法是使用头文件并将其包含在file2.cppfile1.cpp

extern const int test;extern int test2;

或者可以用显式的externfile1.cpp声明const变量

尽管这是一个非常古老的问题,有多个可接受的答案,但我想分享如何解决模糊“未定义的引用”错误。

不同版本的库

我使用别名来引用std::filesystem::path:文件系统自C++17以来就在标准库中,但我的程序需要C++14,所以我决定使用变量别名:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)using path_t = std::experimental::filesystem::path;#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)using path_t = std::filesystem::path;#endif

假设我有三个文件:main.cpp,file. h,file.cpp:

  • 文件h#包括<实验::f系统>并包含上面的代码
  • file.cpp,file. h的实现,#support的“文件h
  • main.cpp#包括<文件系统>和“文件h

注意main.cpp和file. h中使用的不同的图书馆。由于main.cpp#在<文件系统>之后包含“文件h”,因此使用的文件系统版本为C++17。我用以下命令编译程序:

$g++ -g -std=c++17 -c main.cpp->将main.cpp编译为main. o
$g++ -g -std=c++17 -c file.cpp->将file.cpp和file. h编译为file. o
$g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs->连接main. o和file. o

这样任何功能包含在file. o中并在main. o中使用,必填项#0给出了“未定义的引用”错误,因为main. o引用了#1,但文件引用了#2

决议

要解决这个问题,我只需要将file. h中的<实验::files系统>更改为

链接到共享库时,请确保不隐藏使用的符号。

gcc的默认行为是所有符号都是可见的。但是,当使用选项-fvisibility=hidden构建翻译单元时,结果共享对象中只有标有__attribute__ ((visibility ("default")))的函数/符号是外部的。

您可以通过调用以下命令来检查您要查找的符号是否为外部符号:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.sonm -D XXX.so | grep MY_SYMBOL

隐藏/本地符号由nm显示,小写符号类型,例如t而不是代码段的'T:

nm XXX.so00000000000005a7 t HIDDEN_SYMBOL00000000000005f8 T VISIBLE_SYMBOL

您还可以使用nm和选项-C来分解名称(如果使用了C++)。

与Windows-dlls类似,可以使用定义标记公共函数,例如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 hidden0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

不同的架构

您可能会看到这样的消息:

library machine type 'x64' conflicts with target machine type 'X86'

在这种情况下,这意味着可用符号用于与您正在编译的架构不同的架构。

在Visual Studio上,这是由于错误的“平台”,您需要选择正确的平台或安装正确版本的库。

Linux,可能是由于错误的库文件夹(例如使用lib而不是lib64)。

在MacOS上,可以选择将两个架构发送到同一个文件中。可能是链接希望两个版本都在那里,但只有一个版本在那里。它也可能是错误的lib/lib64文件夹的问题。

函数或类方法在源文件中使用inline说明符定义。

举个例子:

main.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;};

Gum. 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)在其定义中为inline,则编译器将内联gum(如果它选择),通过:-

  • 没有发出gum的任何唯一定义,因此
  • 不发出链接器可以引用gum定义的任何符号,而是
  • 将所有对gum的调用替换为gum的编译主体的内联副本。

因此,如果您在源文件gum.cpp中内联定义gum,则编译为对象文件gum.o,其中对gum的所有调用都内联并且没有定义链接器可以引用gum的符号。当您将gum.o与另一个目标文件链接到程序中,例如main.o引用外部符号gum,链接器无法解析这些引用。因此链接失败:

编译:

g++ -c  main.cpp foo.cpp gum.cpp

链接:

$ g++ -o prog main.o foo.o gum.omain.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定义为inline。这意味着它的内联定义需要存在于您在每个源文件中包括报头文件中您编译其中可以调用gum。做两件事之一:

要么不要内联定义

从源文件定义中删除inline说明符:

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.cppimk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.oimk@imk-Inspiron-7559:~/develop/so/scrap1$ ./progvoid gum()void foo::bar() const

成功。

或正确内联

头文件中的内联定义:

foo. h(2)

#pragma once#include <iostream>
struct foo {void bar() const  { // In-class definition is implicitly inlinestd::cout << __PRETTY_FUNCTION__ << std::endl;}};// Alternatively...#if 0struct foo {void bar() const;};inline void foo::bar() const  {std::cout << __PRETTY_FUNCTION__ << std::endl;}#endif

Gum. h(2)

#pragma once#include <iostream>
inline void gum() {std::cout << __PRETTY_FUNCTION__ << std::endl;}

现在我们不需要foo.cppgum.cpp

$ g++ -c main.cpp$ g++ -o prog main.o$ ./progvoid gum()void foo::bar() const

在头文件中声明函数的原型时,我遇到了这个问题:

int createBackground(VertexArray rVA, IntRect arena);

但随后使用带有第一个参数的引用在其他地方定义函数:

int createBackground(VertexArray& rVA, IntRect arena) {}

原型在第一个参数中没有使用引用,而定义使用了引用,这一事实导致了这个问题。当我将两者更改为正确匹配包含引用或不包含引用时,问题得到了解决。

干杯。

我的例子:

头文件

class GameCharacter : public GamePart{private:static vector<GameCharacter*> characterList;...}

. cpp文件:

vector<GameCharacter*> characterList;

这产生了一个“未定义”的加载程序错误,因为“字符列表”被声明为静态成员变量,但被定义为全局变量。

我加上这个是因为,虽然其他人在一长串需要注意的事情中列出了这个案例,但这个列表没有给出例子。这是一个需要更多关注的例子,尤其是在C++中。

修复方法是向全局变量添加限定条件以定义静态数据成员:

vector<GameCharacter*> GameCharacter::characterList;

同时保持标题相同。

当我们在程序中引用对象名(类、函数、变量等)时,会发生“未定义引用”错误,链接器在尝试在所有链接的对象文件和库中搜索它时找不到它的定义。

因此,当链接器找不到链接对象的定义时,它会发出“未定义引用”错误。从定义上看,这个错误发生在链接过程的后期阶段。导致“未定义引用”错误的原因有很多。

一些可能的原因(更频繁):

#1)没有为对象提供定义

这是导致“未定义引用”错误的最简单原因。程序员只是忘记定义对象。

考虑下面的C++程序。这里我们只指定了函数的原型,然后在主函数中使用它。

#include <iostream>int func1();int main(){     
func1();}

输出:

main.cpp:(.text+0x5): undefined reference to 'func1()'collect2: error ld returned 1 exit status

因此,当我们编译此程序时,会发出链接器错误,即“未定义的对'函数1()'的引用”。

为了消除这个错误,我们通过提供函数的定义来纠正程序如下。现在程序给出了适当的输出。

#include <iostream>using namespace std;int func1(); 
int main(){     
func1();}int func1(){cout<<"hello, world!!";}

输出:

hello, world!!

#2)使用的对象定义错误(签名不匹配)

“未定义引用”错误的另一个原因是我们指定了错误的定义。我们在程序中使用任何对象,其定义都是不同的。

考虑下面的C++程序。这里我们调用了函数1()。它的原型是int函数1()。但它的定义与其原型不匹配。正如我们所看到的,函数的定义包含函数的参数。

因此,当程序编译时,由于原型和函数调用匹配,编译成功。但是当链接器试图将函数调用与其定义链接时,它发现问题并将错误作为“未定义引用”发出。

#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()”函数。当我们分别编译这些文件时,第一个文件为print函数提供了“未定义的引用”,而第二个文件为main函数提供了“未定义的引用”。

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)不编译依赖文件

程序员必须确保我们事先编译了项目的所有依赖项,这样当我们编译项目时,编译器就会找到所有的依赖项并成功编译。如果缺少任何依赖项,那么编译器就会给出“未定义的引用”。

除了上面讨论的原因之外,“未定义引用”错误可能发生在许多其他情况下。但底线是程序员弄错了,为了防止这种错误,应该纠正它们。

在我的例子中,语法是正确的,但是当一个类在同一个DLL中调用第二个类时,我遇到了这个错误。第二个类的CPP文件在Visual Studio中的Properties->Item Type错误,在我的例子中,它被设置为C/C++ Header而不是正确的C/C++ compiler,所以编译器在构建CPP文件时没有编译它并导致错误LNK2019

这里有一个例子,假设语法是正确的,您应该通过更改属性中的项类型来获取错误

//class A header fileclass ClassB; // FORWARD DECLERATIONclass ClassA{public:ClassB* bObj;ClassA(HINSTANCE hDLL) ;//  member functions}--------------------------------//class A cpp fileClassA::ClassA(HINSTANCE hDLL){bObj = new ClassB();// LNK2019 occures herebObj ->somefunction();// LNK2019 occures here}/*************************/
//classB Header filestruct 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(){}

当你使用错误的编译器来构建你的程序时

如果您使用的是gccclang编译器套件,您应该根据您使用的语言使用正确的编译器驱动程序。使用g++clang++编译并链接C++程序。改为使用gccclang将导致对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 PIEcollect2: error: ld returned 1 exit status

一些错字错误需要考虑:(作为初学者,我经常遇到)

  • 如果您使用的是类:检查您是否没有忘记在定义函数的cpp文件中的函数名称之前的“class name::”。
  • 如果您使用前置声明:请务必声明正确的类型。例如:如果您想前置声明“struct”,请使用“struct”而不是“class”。

将Visual Studio Code与Code Runner扩展和多个. c或. cpp文件一起使用

附带的Code Runner仅适用于具有单个源文件的编译程序。它不适合与多个源文件一起使用。您应该使用不同的扩展名,例如C/C++Makefile项目扩展名或CMake工具扩展名,或者修复Code Runner扩展以处理多个文件,或者只是手动编辑您的. json配置文件。