什么是未定义的引用/未解析的外部符号错误,如何解决?

What is an undefined reference/unresolved external symbol error and how do I fix it?

提问人:Luchian Grigore 提问时间:9/25/2012 最后编辑:Jan SchultkeLuchian Grigore 更新时间:8/28/2023 访问量:941341

问:

什么是未定义的参考/未解析的外部符号错误?常见原因是什么,如何修复和防止这些错误?

C 链接器错误 定义引用 C++常见问题解答 未解析外部 可视化工作室

评论

1赞 Ben Voigt 4/28/2015
@jave.web:当这种情况发生时,程序员通常会注意到他没有指针,也无法访问类成员。完成编译的情况非常罕见,只有在链接期间才会失败,当非静态成员函数缺少其限定名称时。this
2赞 RoG 10/20/2016
@jave.web:这正是我的问题。谢谢!我是 cpp 的新手,但据我所知,我遇到了 Ben Voigt 所说的非常罕见的问题。我认为您的解决方案将是一个很好的答案。
1赞 Luchian Grigore 10/21/2016
@Snaptastic看到 stackoverflow.com/a/12574407/673730 - 一个常见的错误是忘记限定名称:)
9赞 Albert van der Horst 8/17/2019
它们可能很有用,就像许多被标记为过于笼统的问题的答案一样。
3赞 Danilo 9/9/2019
老实说,我希望看到最小的可重现示例作为我们对大多数新用户的要求。我不是故意的,它只是——我们不能指望人们遵守我们没有强加给自己的规则。

答:

1010赞 Luchian Grigore 9/25/2012 #1

假设您有以下代码:

// a.cpp
int get() { return 0; }
// b.cpp
int get(); // usually, one doesn't write this directly, but gets these
           // declarations from included header files
int x = get();

编译时,编译器只是假设符号在某处定义,但它还不关心在哪里。链接阶段负责查找符号并正确链接由 和 生成的对象文件。b.cppget()a.cppb.cpp

如果未定义,则会收到链接器错误,指出“未定义的引用”或“未解析的外部符号”。a.cppget

C++ 标准措辞

编译 C++ 程序分几个阶段进行,在 [lex.phases] 中指定,最后一个阶段是相关的:

9. 解析所有外部实体引用。 链接库组件以满足对当前转换中未定义的实体的外部引用。 所有这些转换器输出都被收集到一个程序映像中,该映像包含在其执行环境中执行所需的信息。

有关这些阶段的摘要,请参阅 Keith Thompson 的回答

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

实践中的链接器错误

如果使用的是 Microsoft Visual Studio,则会看到项目生成文件。它们包含导出符号表和导入符号表。导入的符号将针对您链接的库进行解析,并且导出的符号将提供给使用该符号的库(如果有)。.lib.lib

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

常见的错误消息是 Microsoft Visual Studio 的 、 和 GCCsymbolNameerror LNK2001error LNK1120error LNK2019undefined 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;
}

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

常见原因

评论

25赞 Michael Burr 12/31/2013
就个人而言,我认为 MS 链接器错误消息与 GCC 错误一样可读。它们还有一个优点,即为未解析的外部名称同时包含已损坏和未损坏的名称。当您需要直接查看库或对象文件以查看可能的问题(例如,调用约定不匹配)时,使用损坏的名称会很有帮助。此外,我不确定哪个版本的 MSVC 在此处产生了错误,但较新的版本包括引用未解析的外部符号的函数的名称(包括损坏和未修复)。
12赞 Pressacco 5/22/2015
大卫·德赖斯代尔(David Drysdale)写了一篇关于链接器如何工作的好文章:链接器初学者指南。鉴于这个问题的主题,我认为它可能会被证明是有用的。
0赞 doug65536 10/15/2016
@TankorSmash使用 gcc 吗?更准确地说是 MinGW。
5赞 Phalgun 2/22/2018
@luchian如果您添加正确的,修复上述错误,那就太好了
2赞 Jake Levi 1/26/2022
我刚刚遇到了编译器错误的另一个可能原因。函数最初被定义为在标头中,但我将其更改为声明,并在源文件中单独定义它。这失败了,出现了编译器错误,直到我从声明和定义中删除了关键字。unresolved symbolinlineunresolved symbolinline
137赞 Luchian Grigore 9/25/2012 #2

未能链接到相应的库/目标文件或编译实现文件

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

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

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

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

这里只是库的裸露名称,没有特定于平台的添加。例如,在 Linux 上,通常会调用库文件,但您只会编写 .在 Windows 上,可以调用相同的文件,但您将使用相同的参数。您可能需要使用 添加可以找到这些文件的目录。确保不要在 或 之后写空格。libraryNamelibfoo.so-lfoofoo.lib-L‹directory›-l-L

对于 Xcode:添加用户标头搜索路径 ->添加库搜索路径 -> 将实际的库引用拖放到项目文件夹中。

MSVS 下,添加到项目中的文件会自动将其目标文件链接在一起,并生成一个文件(通常使用)。要在单独的工程中使用符号,您需要 需要将文件包含在项目设置中。这是在项目属性的“链接器”部分中完成的。(文件的路径应为 添加于 ) 使用随文件提供的第三方库时,如果不这样做,通常会导致错误。liblibInput -> Additional DependencieslibLinker -> General -> Additional Library Directorieslib

也可能是忘记将文件添加到编译中,在这种情况下,不会生成目标文件。在 gcc 中,您可以将文件添加到命令行中。在 MSVS 中,将文件添加到项目中将使其自动编译(尽管可以手动将文件单独从生成中排除)。

在 Windows 编程中,未链接必要库的明显标志是未解析符号的名称以 开头。在文档中查找函数的名称,它应该说明您需要使用哪个库。例如,MSDN 将信息放在名为“库”的部分中每个函数底部的框中。__imp_

评论

4赞 M.M 11/5/2019
如果你能明确地涵盖 instead 的常见错误(初学者在他们的项目变得如此之大以至于构建 .o 文件之前经常这样做),那就太好了。gcc main.cgcc main.c other.c
117赞 Luchian Grigore 9/25/2012 #3

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

典型的变量声明是

extern int x;

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

int x;

例如,以下情况将生成错误:

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

类似的评论也适用于函数。在不定义函数的情况下声明函数会导致错误:

void foo(); // declaration only
int 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)
                          

其他不匹配的例子包括

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

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

评论

1赞 Laurie Stearn 3/30/2018
在 VS 中,与未添加到源目录中的标头中的 cpp 文件匹配的文件也属于缺少定义的类别。#includes
200赞 Luchian Grigore 9/25/2012 #4

班级成员:

纯析构函数需要实现。virtual

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

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

发生这种情况的原因是,当对象被隐式销毁时,会调用基类析构函数,因此需要定义。

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

这类似于没有定义的非方法,但增加了推理 纯声明生成一个虚拟的 vtable,如果不使用该函数,您可能会收到链接器错误:virtual

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

为此,请声明为 pure: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

可以为类定义中整数或枚举类型的数据成员提供初始值设定项;但是,此成员的 ODR 使用仍需要如上所述的命名空间范围定义。C++11 允许在类内对所有数据成员进行初始化。staticconststatic const

评论

0赞 Deduplicator 9/21/2014
只是想你可能想强调两者都是可能的,而 dtor 实际上也不例外。(乍一看,从你的措辞中看不出来。
68赞 Luchian Grigore 9/25/2012 #5

模板实现不可见。

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

专用模板可以在实现文件中实现,并且实现不必可见,但必须事先声明专用化。

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

81赞 Luchian Grigore 9/25/2012 #6

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

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

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

C++ 链接器需要修改名称,因此必须将函数声明为:

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

等效地,函数(或变量)不是在 C 程序中定义的,而是在 C++ 中定义的,但带有 C 链接:void foo()

extern "C" void foo();

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

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

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

评论

8赞 Bentoy13 7/8/2015
或者相反,如果您开发一个 C 库,一个很好的规则是通过用 and 括住所有导出的声明来保护头文件(是真正的回车符,但我无法在注释中正确编写)。#ifdef __cplusplus [\n] extern"C" { [\n] #endif#ifdef __cplusplus [\n] } [\n] #endif[\n]
0赞 zanbri 11/23/2016
如上所述,此处的“创建混合语言标头”部分有所帮助:oracle.com/technetwork/articles/servers-storage-dev/...
0赞 ComFreek 3/25/2020
如果您意外地包含被外部 C 包围的普通 C++ 头文件,也会发生这种情况。extern "C" { #include <myCppHeader.h> }
62赞 Luchian Grigore 9/25/2012 #7

错误地跨模块/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
{
};

评论

4赞 rubenvb 12/23/2012
为了完整起见,此答案应提及 GCC 和 Windows 的文件,因为它们也会影响符号名称和状态。visibility.def
0赞 Luchian Grigore 12/29/2012
@rubenvb我已经很久没有使用过文件了。随意添加答案或编辑此答案。.def
75赞 sgryzko 12/4/2013 #8

如果所有其他方法都失败,请重新编译。

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

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

45赞 πάντα ῥεῖ 3/4/2014 #9

WinMain@16或类似的“不寻常”main() 入口点引用的未定义引用(尤其是对于 )。

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

如果 IDE 支持普通控制台项目,则可能需要选择此项目类型,而不是 Windows 应用程序项目。


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

评论

2赞 chris 3/11/2014
忍不住指出这个问题,以及这样一个事实,即这通常是由于根本没有主要功能而不是没有。有效的 C++ 程序需要一个 .WinMainmain
26赞 kiriloff 4/4/2014 #10

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

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

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

评论

0赞 IInspectable 12/2/2023
这读起来很像“我不知道我在做什么,所以我只是尝试随机的事情,直到链接者停止抱怨;我不知道我让哪个问题消失了”。这没有用。
41赞 Dula 6/19/2014 #11

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

103赞 Svalorzen 7/10/2014 #12

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

如果库相互依赖,则链接库的顺序确实很重要。通常,如果 library 依赖于 library ,则 MUST 出现在链接器标志中。ABlibAlibB

例如:

// 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.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - 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

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

评论

0赞 Marco Sulla 9/18/2016
奇怪的事实是,就我而言,我有一个依赖于共享库的目标文件。我不得不修改 Makefile 并将库放在 Debian 上使用 gcc 4.8.4 的对象之后。在 Centos 6.5 和 gcc 4.4 上,Makefile 没有问题。
0赞 prapin 10/19/2023
我认为命令行中库的顺序只对 GNU 链接器 (GCC) 重要。对于 MSVC,该订单无效,对于 Apple 链接器也是如此。
0赞 Cloud Cho 12/7/2023
如果按“链接”顺序有参考,请分享。
36赞 Niall 9/9/2014 #13

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

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

除了库路径(包括库的目录)之外,这应该是库的全名。

81赞 5 revs, 3 users 97%Kastaneda #14

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

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

注意:我使用 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;
}

// 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

链接器查看导出的符号并将其链接。现在我们尝试取消注释 src2.cpp 中的行,如下所示

// 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

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

$ 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

    .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:
; ...

我们更改了 local_var_name 的可见性,并将其值设置为 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)

链接

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

并运行它

$ ./prog 
123456789

好的,我们破解它:)

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

评论

0赞 Cloud Cho 12/7/2023
关于“在文本编辑器中打开src1.s并更改”,手动修改S文件而不是CPP文件有什么需要注意或缺点吗?
29赞 brettwhiteman 1/3/2015 #15

编译器/IDE 中的错误

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

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

  • 清理项目(某些 IDE 可以选择执行此操作,您也可以 通过删除目标文件手动执行此操作)
  • 尝试开始一个新项目, 从原始代码复制所有源代码。

评论

5赞 JDiMatteo 2/2/2016
相信你的工具坏了很可能会让你远离真正的原因。您犯错的可能性比编译器导致您的问题的可能性要大得多。清理解决方案或重新创建生成配置可能会修复生成错误,但这并不意味着编译器中存在 bug。链接的“原来这是一个错误”未得到 Microsoft 的确认,并且不可重现。
4赞 brettwhiteman 2/2/2016
@JDiMatteo 这个问题有 21 个答案,因此大量答案不会是“可能”的解决方案。如果您忽略所有低于可能性阈值的答案,那么此页面实际上将变得毫无用处,因为无论如何,大多数常见情况都很容易被发现。
35赞 Malvineous 1/17/2015 #16

Visual Studio NuGet 包需要针对新的工具集版本进行更新

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

正确的解决方案是希望开发人员发布更新的软件包然后升级,但它通过入侵 VS2013 的额外设置,指向 VS2012 库文件对我有用。

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

评论

1赞 Luchian Grigore 1/17/2015
这似乎过于具体 - 也许一个新线程会更适合这个答案。
3赞 Malvineous 1/17/2015
@LuchianGrigore:我确实想在这里发帖,因为这个问题正是这个问题,但它被标记为这个问题的重复,所以我无法在那里回答。所以我把我的答案贴在这里。
0赞 Luchian Grigore 1/17/2015
这个问题已经有了公认的答案。它被标记为重复,因为上面列出了一般原因。如果我们在这里对未包含的库的每个问题都有一个答案,会发生什么?
6赞 Malvineous 1/17/2015
@LuchianGrigore:此问题并非特定于库,它会影响使用 Visual Studio 包管理系统的所有库。我只是碰巧发现了另一个问题,因为我们俩都对 libpng 有问题。我对 libxml2、libiconv 和 glew 也有同样的问题(使用相同的解决方案)。这个问题是关于 Visual Studio 的包管理系统的问题,我的回答解释了原因并提供了解决方法。有人只是看到“未解决的外部”,并认为这是一个标准的链接器问题,而实际上它是一个包管理问题。
20赞 JDiMatteo 3/31/2015 #17

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

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

INPUT (libtbb.so.2)

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

cp libtbb.so.2 libtbb.so

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

65赞 Nima Soroush 4/14/2015 #18

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

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

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

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

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

此搜索过程称为解析。

D.最后,为什么是未解析的外部符号?如果链接器找不到内部没有定义的符号的外部定义,则会报告“未解析的外部符号”错误。

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

  1. 存在定义

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

int foo()
{
    return 0;
}

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

void foo();

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

void bar()
{
    foo();
}

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

  1. 定义不存在

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

30赞 Niall 7/27/2015 #19

使用链接器帮助诊断错误

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

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

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

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

35赞 user4272649 8/11/2015 #20

假设你有一个用 c++ 编写的大项目,它有一千个 .cpp 文件和一千个 .h 文件。假设该项目还依赖于十个静态库。假设我们在 Windows 上,并在 Visual Studio 20xx 中构建项目。当您按 Ctrl + F7 Visual Studio 开始编译整个解决方案时(假设解决方案中只有一个项目)

编译是什么意思?

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

编译的第二步由 Linker 完成,Linker 应该合并所有目标文件,并最终构建输出(可以是可执行文件或库)

链接项目的步骤

  • 解析所有对象文件并找到仅在标头中声明的定义(例如:前面的答案中提到的类的一个方法的代码,或者事件是作为类内部成员的静态变量的初始化)
  • 如果在目标文件中找不到一个符号,则还会在“其他库”中搜索该符号。要向项目添加新库,请执行以下操作:配置属性 -> VC++ 目录 -> 库目录,并在此处指定了用于搜索的附加文件夹和配置属性 -> 链接器 -> 输入用于指定库的名称。 - 如果链接器找不到您在一个 .cpp 中写入的符号,他会引发链接器时间错误,这听起来可能像error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

观察

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

如何解决这种错误

编译器时间错误:

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

链接器时间错误

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

评论

0赞 VP. 8/13/2015
你的答案不是特定于 Visual Studio 的吗?该问题没有指定任何 IDE/编译器工具,因此它使您的答案对非 visual-studio 部分毫无用处。
0赞 8/13/2015
你是对的。但是,每个IDE编译/链接过程的完成方式都略有不同。但是文件的处理方式完全相同(甚至 g++ 在解析标志时也会做同样的事情。
0赞 VP. 8/13/2015
问题实际上不在于IDE,而在于链接问题的答案。链接问题与 IDE 无关,而是与编译器和构建过程有关。
0赞 8/13/2015
但是构建/链接过程是在 g++/Visual Studio(Microsoft 为 VS 提供的编译器)/Eclipse/Net Beans 中以同样的方式完成的
25赞 Plankalkül 9/10/2015 #21

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

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

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

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

14赞 Niall 2/24/2016 #22

清洁和重建

对构建进行“清理”可以删除可能残留在以前的构建、失败构建、不完整构建和其他与构建系统相关的构建问题中的“枯木”。

通常,IDE 或构建将包含某种形式的“干净”函数,但这可能无法正确配置(例如在手动生成文件中)或可能失败(例如,中间或生成的二进制文件是只读的)。

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

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

19赞 Niall 3/9/2016 #23

与模板交朋友...

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

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

被声明为非模板函数。对于与 一起使用的每种类型,都需要有一个非模板化的 .例如,如果声明了类型,则必须有如下运算符实现;operator<<TFoooperator<<Foo<int>

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

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

若要更正此问题,可以在类型之前声明一个模板运算符,然后将相应的实例化声明为友元。语法有点笨拙,但如下所示;Foo

// forward declare the Foo
template <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
}

上面的代码将算子的友情限制在 的相应实例化上,即实例化仅限于访问 的实例化的私有成员。Foooperator<< <int>Foo<int>

替代方案包括;

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

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
    
  • 或者,可以在类定义中以内联方式完成实现;operator<<

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

请注意,当运算符(或函数)的声明仅出现在类中时,该名称不可用于“正常”查找,仅可用于 cppreference 中的参数相关查找;

在类或类模板 X 中的友元声明中首次声明的名称将成为 X 最内层封闭命名空间的成员,但无法进行查找(考虑 X 的参数相关查找除外),除非在命名空间范围内提供匹配的声明......

cppreferenceC++ FAQ 上有关于模板朋友的进一步阅读。

显示上述技术的代码列表


作为失败代码示例的旁注;G++ 对此发出如下警告

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)

15赞 Niall 4/7/2016 #24

定义不一致UNICODE

Windows UNICODE 版本是使用 etc. 定义为 etc 构建的。当未使用 defined as 构建 build with defined as 等时。这些和定义会影响所有T”字符串类型;和他们的麋鹿。TCHARwchar_tUNICODETCHARcharUNICODE_UNICODELPTSTRLPCTSTR

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

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

要更正此问题,请使用一致的定义 (和 ) 构建所有必需的库和项目。UNICODE_UNICODE

  1. 这可以通过以下任何一种方式完成;

    #define UNICODE
    #define _UNICODE
    
  2. 或在项目设置中;

    常规>项目属性>项目默认值>字符集

  3. 或者在命令行上;

    /DUNICODE /D_UNICODE
    

如果不打算使用 UNICODE,请确保未设置定义,和/或在项目中使用多字符设置并一致地应用,则替代方法也适用。

不要忘记在“发布”和“调试”版本之间保持一致。

16赞 fafaro 3/3/2017 #25

当包含路径不同时

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

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

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

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

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

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

你可能会问,一个人怎么会陷入这样的境地?当然包括路径!如果在编译共享库时,包含路径通向并且您最终在自己的程序中使用,那么您将不知道发生了什么(双关语)。header1.hheader2.h

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

举例进一步阐述

我有两个项目:和.这两个项目都依赖于 .假设库导出以下函数:graphics.libmain.execommon_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(...);
}

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

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

调试链接器

如果你使用的是 Visual Studio,则 DUMPBIN 是你的朋友。我敢肯定其他编译器还有其他类似的工具。

过程是这样的:

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

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

编辑1:重写了第一部分,使其更易于理解。请在下面发表评论,让我知道是否还有其他需要修复的问题。谢谢!

24赞 Mike Kinghan 4/9/2017 #26

链接在引用库的目标文件之前使用库

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

示例在 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

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

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

$ 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-config

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

解释

从现在开始,阅读是可选的。

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

首先,示例 1,使用静态库my_lib.a

静态库是目标文件的索引存档。当链接器 在链接序列中发现并找出这指的是 到静态库,它想知道你的程序是否 需要 中的任何对象文件。-lmy_lib./libmy_lib.alibmy_lib.a

中只有 目标文件,即 ,并且只定义了一件事 中,即函数。libmy_lib.amy_lib.omy_lib.ohw

链接器将决定您的程序是否需要,当且仅当它已经知道时 您的程序在它已有的一个或多个目标文件中引用 添加到程序中,并且没有添加任何对象文件 包含 的定义。my_lib.ohwhw

如果为 true,则链接器将从库中提取 将其添加到您的程序中。然后,您的程序包含 的定义,因此 其对 的引用已解析my_lib.ohwhw

当您尝试链接程序时,例如:

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

链接器在看到 时尚未添加到程序中。因为在那个时候,它还没有看到. 您的程序尚未对 : it 进行任何引用 根本没有做任何引用,因为它所做的所有引用 在 .eg1.o-lmy_libeg1.ohweg1.o

因此,链接器不会添加到程序中,也没有进一步的 用于 .my_lib.olibmy_lib.a

接下来,它找到 ,并将其添加到程序中。中的对象文件 联动序列始终添加到程序中。现在,该程序使 对 的引用,但不包含 的定义 ;但 联动序列中没有任何东西可以提供缺失的东西 定义。对 的引用最终无法解析,并且链接失败。eg1.ohwhwhw

第二,示例 2,使用共享库libz

共享库不是对象文件或类似文件的存档。它 更像是一个没有功能的程序,并且 而是公开它定义的多个其他符号,以便其他 程序可以在运行时使用它们。main

如今,许多 Linux 发行版都配置了他们的 GCC 工具链,以便其语言驱动程序(、等) 指示系统链接器 () 根据需要链接共享库。 你已经有了其中一个发行版。gccg++gfortranld

这意味着,当链接器在链接序列中找到,并发现这指的是 对于共享库(例如),它想知道它添加到程序中但尚未定义的任何引用是否具有由-lz/usr/lib/x86_64-linux-gnu/libz.solibz

如果为 true,则链接器不会从 和 中复制任何块 将它们添加到您的程序中;相反,它只会修改程序的代码 因此:-libz

  • 在运行时,系统程序加载器会将 每当程序加载程序的副本时,运行它的过程与程序相同。libz

  • 在运行时,每当程序引用 中定义的内容时,该引用都会使用 中 的副本导出的定义 同样的过程。libzlibz

您的程序只想引用一个定义由 导出的事物,由 即函数,在 中只被引用一次。 如果链接器将该引用添加到程序中,然后找到定义 导出者 ,引用被解析libzzlibVersioneg2.clibz

但是当您尝试链接程序时,例如:

gcc -o eg2 -lz eg2.o

事件的顺序是错误的,与示例 1 相同。 在链接器找到 时,没有对任何内容的引用 在节目中:他们都在,这还没有被看到。所以 链接器认为它对 没有用处。当它到达时,将其添加到程序中, 然后有未定义的引用,联动序列完成; 该引用未解析,链接失败。-lzeg2.olibzeg2.ozlibVersion

最后,示例 2 的变体现在有一个显而易见的解释。 shell-expansion 后:pkg-config

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 系统将静态和共享链接起来是很正常的 库采用不同的规则。链接序列中的静态库 在示例 1 中解释的按需基础上,但共享库是无条件链接的。

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

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

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

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

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

如果我只是这样做:

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

当然,GCC 必须首先编译,然后链接结果 带有 的对象文件。那么它怎么可能不知道那个目标文件 在进行链接时需要吗?eg1.clibmy_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':

另请参阅

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

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

14赞 Andreas H. 8/3/2017 #27

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

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

例:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}

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

extern const int test;
extern int test2;

或者,可以在 file1.cpp 中声明变量,并显式constextern

10赞 Stypox 8/28/2018 #28

尽管这是一个相当古老的问题,有多个公认的答案,但我想分享如何解决一个晦涩难懂的“未定义引用”错误。

不同版本的库

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

#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:

  • file.h #include 的<experimental::filesystem>并包含上面的代码
  • file.cpp,file.h 的实现,#include 的“file.h"
  • main.cpp #include 的<文件系统>和“file.h"

请注意 main.cpp 和 file.h 中使用的不同库。由于 main.cpp #include 在 <filesystem> 之后加上 “file.h”,因此那里使用的文件系统版本是 C++17 版本。我曾经使用以下命令编译程序:

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

g++ -g -std=c++17 -c file.cppg++ -g -std=c++17 -o executable main.o file.o -lstdc++fs

这样,file.o 中包含的和 main.o 中使用的任何需要 path_t 的函数都会出现“未定义的引用”错误,因为 main.o 引用了 std::filesystem::p ath,但 file.o 引用了 std::experimental::filesystem::p ath

分辨率

要解决这个问题,我只需要将 file.h 中的 <experimental::filesystem>更改为 <filesystem>

8赞 ead 9/7/2018 #29

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

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

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

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

隐藏/本地符号以小写符号类型显示,例如,代码段代替“T”:nmt

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

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

与 Windows-dll 类似,人们会用定义来标记公共函数,例如定义为: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=hiddennm

若要查找对象文件中的哪些符号是隐藏的,请运行:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
3赞 Matthieu Brucher 12/11/2018 #30

不同的架构

您可能会看到如下消息:

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

在这种情况下,这意味着可用符号的体系结构与要编译的体系结构不同。

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

在 Linux 上,这可能是由于错误的库文件夹(例如,使用 而不是)。liblib64

在 MacOS 上,可以选择在同一文件中提供两种架构。该链接可能期望两个版本都存在,但只有一个版本存在。也可能是选取库的错误/文件夹的问题。liblib64

6赞 Mike Kinghan 1/22/2019 #31

函数或类方法在源文件中使用说明符定义。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;
};

口香糖 (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;
}

口香糖.cpp (1)

#include "gum.h"
#include <iostream>

inline /* <- wrong! */ void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

如果指定 (类似地, ) 是其定义,则 编译器将通过以下方式内联(如果它选择):-gumfoo::barinlinegum

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

因此,如果在源文件中定义内联,则它是 编译为一个对象文件,其中所有对 的调用都内联在其中 并且没有定义链接器可以引用的符号。当你 与另一个目标文件一起链接到一个程序中,例如 引用外部符号时,链接器无法解析 这些参考资料。所以联动失败:gumgum.cppgum.ogumgumgum.omain.ogum

编译:

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

链接:

$ g++ -o prog main.o foo.o gum.o
main.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

您只能进行定义,就好像编译器可以在每个可能被调用的源文件中看到其定义一样。这意味着它的内联定义需要存在于每个源文件中包含的文件中 你编译其中可以调用。执行以下两项操作之一:guminlinegumgum

要么不内联定义

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

FOO.cpp (2)

#include "foo.h"
#include <iostream>

void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

口香糖.cpp (2)

#include "gum.h"
#include <iostream>

void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

用它重建:

$ g++ -c  main.cpp foo.cpp gum.cpp
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const

成功。

或正确内联

头文件中的内联定义:

foo.h (2)

#pragma once
#include <iostream>

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

口香糖 (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
$ ./prog
void gum()
void foo::bar() const

评论

0赞 Peter - Reinstate Monica 6/30/2022
值得注意的是,如果将定义放在标题中但省略了内联说明符,则会出现相反的错误:重复符号;-)。有趣的是,唯一可以保证的效果是它使定义文件成为静态文件。(特别是,不保证实际内联任何内容;-)。inlineinline
0赞 the-wastelander 2/29/2020 #32

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

int createBackground(VertexArray rVA, IntRect arena);

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

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

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

干杯。

1赞 A. P. Damien 5/31/2020 #33

我的例子:

头文件

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

.cpp 文件:

vector<GameCharacter*> characterList;

这会产生“未定义”加载器错误,因为“characterList”被声明为静态成员变量,但被定义为全局变量。

我之所以添加这个,是因为 -- 虽然其他人把这个案例列在一长串需要注意的事项中 -- 但这个列表没有给出例子。这是一个需要寻找的更多内容的例子,尤其是在 C++ 中。

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

vector<GameCharacter*> GameCharacter::characterList;

同时保持标题相同。

评论

0赞 void 11/22/2020
此答案不会为此问题添加合适的信息。您使用非标准宏 (?) 定义,如 TRUE。因此,在这里搜索答案的用户可能会被您对向量和继承的使用所淹没。
8赞 Zig Razor 10/22/2020 #34

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

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

一些可能的原因(更常见):

#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()”函数。当我们单独编译这些文件时,第一个文件为 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) 错误的项目类型

当我们在 C++ IDE(如 Visual Studio)中指定错误的项目类型并尝试执行项目不希望做的事情时,我们会得到“未定义的引用”。

#5) 没有库

如果程序员没有正确指定库路径或完全忘记指定它,那么我们就会从库中获得程序使用的所有引用的“未定义引用”。

#6) 依赖文件未编译

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

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

0赞 ma1169 2/12/2021 #35

就我而言,语法是正确的,但是当一个类调用同一DLL中的第二个类时,我遇到了该错误。第二个类的 CPP 文件在 Visual Studio 中出错,在我的情况下,它被设置为而不是正确的文件,因此编译器在构建 CPP 文件时没有编译它并导致错误LNK2019Properties->Item TypeC/C++ HeaderC/C++ compiler

下面是一个示例,假设语法正确,您应该通过更改属性中的项类型来获得错误

//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(){}
3赞 n. m. could be an AI 4/24/2021 #36

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

如果您使用的是编译器套件,则应根据所使用的语言使用正确的编译器驱动程序。使用 或 编译和链接 C++ 程序。改用 or 将导致对 C++ 标准库符号的引用未定义。例:gccclangg++clang++gccclang

$ 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 PIE 
collect2: error: ld returned 1 exit status
5赞 AliceTheCat 10/18/2021 #37

需要考虑的一些错别字错误:(作为初学者,我经常发生)

  • 如果您使用的是类:检查您是否没有在定义函数的 cpp 文件中的函数名称之前忘记“classname::”。
  • 如果使用正向声明:请确保声明正确的类型。例如:如果要转发声明“结构”,请使用“struct”而不是“class”。

评论

0赞 Mark Ransom 9/27/2022
实际上,和之间的唯一区别是成员默认为公共还是私有。我无法想象混淆两者会导致问题错误的例子。structclass
0赞 prapin 10/19/2023
关于 / forward 声明:只有 MSVC 会在混合关键字时生成链接错误。在这方面,MSVC 不遵循 C++ 标准,但 Microsoft 没有计划修复此错误,因为这会破坏 ABI 兼容性。structclass
0赞 prapin 10/19/2023
我建议你添加一些关于 / 的解释。@MarkRansom:这是因为 MSVC 以不同的方式处理这两个关键字,这与 GCC 或 Clang 不同。您是唯一提到该链接错误的答案,但是我已经在我们的跨平台公司代码库中遇到过几次了。由于 Windows 不是我们的主要开发平台,我们通常会在 CD/CI 中推送提交后发现它。structclass
0赞 Mark Ransom 10/19/2023
@prapin我不知道 MSVC 对结构与.class的不同处理,感谢您的教育。无论您使用哪种编译器,使用哪个关键字的一致性应该是常识。
3赞 n. m. could be an AI 5/21/2022 #38

将 Visual Studio Code 与代码运行程序扩展和多个 .c 或 .cpp 文件一起使用

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

0赞 n. m. could be an AI 12/29/2022 #39

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

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

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

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

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