【问题标题】:What is an undefined reference/unresolved external symbol error and how do I fix it?什么是未定义的引用/未解决的外部符号错误,我该如何解决?
【发布时间】:2023-12-27 15:36:01
【问题描述】:

什么是未定义的引用/未解决的外部符号错误?有哪些常见原因以及如何解决/预防它们?

【问题讨论】:

  • @LuchianGrigore '随意添加答案'如果您愿意,我更愿意添加相关链接(恕我直言)您的主要答案。
  • @jave.web:虽然确实发生了这种情况,但程序员通常会注意到他没有this 指针,也无法访问类成员。当非静态成员函数缺少其限定名时,完成编译并且仅在链接期间失败是非常罕见的。
  • @jave.web:这正是我的问题。谢谢!我是 cpp 的新手,但据我所知,我遇到了 Ben Voigt 所说的非常罕见的确切问题。我认为您的解决方案将是一个很好的答案。
  • 它们可能很有用,就像对标记为过于笼统的问题的许多答案一样。
  • 老实说,我希望看到最小的可重现示例作为我们对大多数新用户的要求。我没有任何意思,只是 - 我们不能指望人们遵守我们没有强加给自己的规则。

标签: c++ linker-errors undefined-reference c++-faq unresolved-external


【解决方案1】:

按照 2.2 (credits to Keith Thompson for the reference):

的规定,编译 C++ 程序需要几个步骤

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

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

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

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

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

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

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

常见的错误消息是error LNK2001error LNK1120error LNK2019(用于Microsoft Visual Studioundefined reference tosymbolName(用于GCC) .

代码:

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

常见原因包括:

【讨论】:

  • 就个人而言,我认为 MS 链接器错误消息与 GCC 错误一样可读。它们还具有包含未解析外部名称的损坏和未损坏名称的优点。当您需要直接查看库或目标文件以了解问题可能是什么(例如,调用约定不匹配)时,使用错误名称会很有帮助。此外,我不确定哪个版本的 MSVC 在这里产生了错误,但较新的版本包括引用未解析外部符号的函数的名称(包括已损坏和未损坏)。
  • David Drysdale 写了一篇关于链接器如何工作的精彩文章:Beginner's Guide to Linkers。鉴于这个问题的主题,我认为它可能会很有用。
  • @TankorSmash 使用 gcc?更准确地说是 MinGW。
  • @luchian 如果您添加正确的,修复上述错误会很好
  • 我刚刚遇到了unresolved symbol 编译器错误的另一个可能原因。函数最初在标头中定义为inline,但我将其更改为声明并在源文件中单独定义。在我从声明和定义中删除 inline 关键字之前,这失败并出现 unresolved symbol 编译器错误。
【解决方案2】:

班级成员:

virtual 析构函数需要实现。

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

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

这是因为在隐式销毁对象时调用了基类析构函数,因此需要定义。

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

这类似于没有定义的非virtual 方法,并添加了以下推理: pure 声明会生成一个虚拟 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

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

【讨论】:

  • 只是想您可能想强调两者都可以,并且 dtor 实际上并不是一个例外。 (乍一看你的措辞并不明显。)
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 如果你能明确地覆盖gcc main.c 而不是gcc main.c other.c 的常见错误会很好(初学者在他们的项目变得如此之大以至于构建.o 文件之前经常这样做)。
【解决方案4】:

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

一个典型的变量声明是

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)
                          

其他不匹配的例子包括

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

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

【讨论】:

  • 在 VS 中,匹配头文件 #includes 而不是 added 到源目录的 cpp 文件也属于缺少定义的类别。
【解决方案5】:

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

如果库相互依赖,则链接库的顺序很重要。一般来说,如果库 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.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

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

【讨论】:

  • 我奇怪的是,就我而言,我有一个依赖于共享库的目标文件。我不得不修改 Makefile 并将库 AFTER 与 gcc 4.8.4 放在 Debian 上。在带有 gcc 4.4 的 Centos 6.5 上,Makefile 没有问题。
【解决方案6】:

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

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

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

好的(没有错误),因为我们只构建目标文件,链接还没有完成。 尝试链接

$ 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

好的,我们破解它:)

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

【讨论】:

    【解决方案7】:

    符号在 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"
    }
    

    【讨论】:

    • 或者相反,如果你开发一个 C 库,一个很好的规则是通过用 #ifdef __cplusplus [\n] extern"C" { [\n] #endif#ifdef __cplusplus [\n] } [\n] #endif 包围所有导出的声明来保护头文件([\n] 是真正的马车返回,但我不能在评论中正确地写这个)。
    • 和上面的评论一样,这里的“创建混合语言标题”部分有所帮助:oracle.com/technetwork/articles/servers-storage-dev/…
    • 如果您不小心将普通 C++ 头文件包含在 extern C 中,也会发生这种情况:extern "C" { #include <myCppHeader.h> }.
    【解决方案8】:

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

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

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

    【讨论】:

      【解决方案9】:

      模板实现不可见。

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

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

      有关进一步解释和另一种可能的解决方案(显式实例化),请参阅this question and answer

      【讨论】:

        【解决方案10】:

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

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

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

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

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

        这个搜索过程称为解析。

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

        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,因为当前搜索范围内不存在该定义。

        【讨论】:

          【解决方案11】:

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

          【讨论】:

          • 为了完整起见,这个答案应该提到 GCC 的 visibility 和 Windows 的 .def 文件,因为这些也会影响符号名称和存在。
          • @rubenvb 我已经很久没有使用过.def 文件了。随意添加答案或编辑此答案。
          【解决方案12】:

          未定义对WinMain@16 或类似的引用'unusual' main() 入口点引用(尤其是)。

          您可能错过了使用实际 IDE 选择正确的项目类型。 IDE 可能想要绑定,例如Windows 应用程序投射到这样的入口点函数(如上面缺少的参考中所指定),而不是常用的int main(int argc, char** argv); 签名。

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


          这里是 case1case2 从一个现实世界问题中得到更详细的处理。

          【讨论】:

          • 不得不指出this question,事实上,这更多是因为没有主函数而不是没有WinMain。有效的 C++ 程序需要 main
          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

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

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

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

            【讨论】:

              【解决方案15】:

              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 年的库,在这种情况下,它起作用了。

              【讨论】:

              • 这似乎过于具体 - 也许一个新线程会是这个答案的更好地方。
              • @LuchianGrigore:我确实想发布here,因为这个问题正是这个问题,但它被标记为这个问题的重复,所以我无法在那里回答。所以我在这里发布了我的答案。
              • 那个问题已经有一个公认的答案。它被标记为重复,因为上面列出了一般原因。如果我们在这里为未包含的库的每个问题提供答案,会发生什么?
              • @LuchianGrigore:此问题并非特定于库,它会影响使用 Visual Studio 包管理系统的所有库。我只是碰巧找到了另一个问题,因为我们都遇到了 libpng 的问题。对于 libxml2、libiconv 和 glew,我也有同样的问题(使用相同的解决方案)。这个问题是关于 Visual Studio 的包管理系统的问题,我的回答解释了原因并提供了解决方法。有人刚刚看到“未解决的外部问题”,并认为这是一个标准的链接器问题,而实际上它是一个包管理问题。
              【解决方案16】:

              假设您有一个用 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 的文件中,该文件包含所有必须为由链接器处理。

              第二步编译由链接器完成。链接器应该合并所有目标文件并最终构建输出(可能是可执行文件或库)

              链接项目的步骤

              • 解析所有目标文件并找到仅在标题中声明的定义(例如:前面的答案中提到的类的一种方法的代码,或事件初始化静态变量,该变量是类中的成员)
              • 如果在目标文件中找不到一个符号,他也会在其他库中进行搜索。对于向项目添加新库配置属性 -> VC++ 目录 - > Library Directories,在这里您指定了用于搜索库和 Configuration properties -> Linker -> Input 的附加文件夹指定库的名称。 - 如果链接器找不到您在一个 .cpp 中写入的符号,他会引发 链接器时间错误,这听起来像 error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

              观察

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

              如何解决此类错误

              编译器时间错误:

              • 确保您编写的 C++ 项目语法正确。

              链接器时间错误

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

              【讨论】:

              • 您的答案不是特定于视觉工作室吗?该问题未指定任何 IDE/编译器工具,因此它使您的答案对非可视化工作室部分毫无用处。
              • 你是对的。但是每个 IDE 的编译/链接过程都略有不同。但是文件的处理方式完全相同(甚至 g++ 在解析标志时也会做同样的事情..)
              • 问题实际上与 IDE 无关,而与链接问题的答案有关。链接问题与 IDE 无关,而与编译器和构建过程有关。
              • 是的。但是构建/链接过程是在 g++/Visual Studio(Microsoft 为 VS 提供的编译器)/Eclipse/Net Beans 中以相同的方式完成的
              【解决方案17】:

              编译器/IDE 中的一个错误

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

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

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

              【讨论】:

              • 相信你的工具坏了很可能会让你远离真正的原因。与编译器导致您的问题相比,您犯错误的可能性要大得多。清理您的解决方案或重新创建您的构建配置可能会修复构建错误,但这并不意味着编译器中存在错误。链接的“原来这是一个错误”未经微软确认且不可重现。
              • @JDiMatteo 这个问题有 21 个答案,因此大量答案不会成为“可能”的解决方案。如果您忽略所有低于您的可能性阈值的答案,那么这个页面实际上变得毫无用处,因为大多数常见案例很容易被发现。
              【解决方案18】:

              使用链接器帮助诊断错误

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

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

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

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

              【讨论】:

                【解决方案19】:

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

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

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

                【讨论】:

                  【解决方案20】:

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

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

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

                  因此,如果您在 5.1.0 之后切换到 GCC 时突然遇到链接器错误,则需要检查一下。

                  【讨论】:

                    【解决方案21】:

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

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

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

                    以及涉及pkg-config 的示例 2 的变体:

                    $ 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 生成的链接命令在您的发行版中, 从左到右消耗链接中的文件 命令行序列。当它发现一个文件引用了 something 并且不包含它的定义,将搜索定义 在右侧的文件中。如果最终找到定义,则 参考解决。如果最后有任何引用仍未解决, 链接失败:链接器不向后搜索。

                    首先,示例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:它 根本没有做任何引用,因为它所做的所有引用 在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 时,no 对任何内容的引用 节目中:都在eg2.o,目前还没有看到。所以 链接器决定它对libz 没有用处。当它到达eg2.o 时,将其添加到程序中, 然后对zlibVersion有未定义的引用,链接序列完成; 该引用未解析,并且链接失败。

                    最后,示例 2 的 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.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':
                    

                    另见

                    The order in which interdependent linked libraries are specified is wrong

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

                    【讨论】:

                      【解决方案22】:

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

                      一些 .so 文件实际上是 GNU ld linker scripts,例如libtbb.so 文件是一个 ASCII 文本文件,内容如下:

                      INPUT (libtbb.so.2)
                      

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

                      cp libtbb.so.2 libtbb.so
                      

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

                      【讨论】:

                        【解决方案23】:

                        结交模板...

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

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

                        operator&lt;&lt; 被声明为非模板函数。对于与Foo 一起使用的每种类型T,都需要有一个非模板化的operator&lt;&lt;。例如,如果声明了一个类型Foo&lt;int&gt;,那么一定有一个操作符实现如下;

                        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
                        }
                        

                        以上代码将操作符的友谊限制为Foo的对应实例化,即operator&lt;&lt; &lt;int&gt;实例化仅限于访问Foo&lt;int&gt;实例化的私有成员。

                        替代方案包括;

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

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

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

                        注意,当操作符(或函数)的声明只出现在类中时,该名称不可用于“正常”查找,只能用于参数依赖查找,来自cppreference

                        首先在类或类模板 X 中的友元声明中声明的名称成为 X 的最内层封闭命名空间的成员,但不能用于查找(除了考虑 X 的参数相关查找),除非在提供了命名空间范围...

                        cppreferenceC++ FAQ有更多关于模板朋友的阅读。

                        Code listing showing the techniques above.


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

                        warning: friend declaration 'std::ostream&amp; operator&lt;&lt;(...)' 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 &lt;&gt; after the function name here)

                        【讨论】:

                          【解决方案24】:

                          当您的包含路径不同时

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

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

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

                          // header1.h
                          typedef int Number;
                          void foo(Number);
                          
                          // header2.h
                          typedef 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(...);
                          }
                          

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

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

                          调试链接器

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

                          流程如下:

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

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

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

                          【讨论】:

                            【解决方案25】:

                            UNICODE 定义不一致

                            Windows UNICODE 构建是使用TCHAR 等定义为wchar_t 等构建的。当不使用UNICODE 构建时定义为使用TCHAR 定义为char 等构建。这些UNICODE 和@ 987654329@定义影响所有"T" string typesLPTSTRLPCTSTR 和他们的麋鹿。

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

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

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

                            1. 这可以用任何一个来完成;

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

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

                            3. 或者在命令行上;

                              /DUNICODE /D_UNICODE
                              

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

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

                            【讨论】:

                              【解决方案26】:

                              清理并重建

                              构建的“清理”可以去除之前构建、失败构建、不完整构建和其他构建系统相关构建问题中可能遗留的“死木”。

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

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

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

                              【讨论】:

                                【解决方案27】:

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

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

                                例子:

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

                                或者,可以使用显式extern 在file1.cpp 中声明const 变量

                                【讨论】:

                                  【解决方案28】:

                                  尽管这是一个很老的问题,但有多个已被接受的答案,我还是想分享一下如何解决晦涩“未定义的引用”错误。

                                  不同版本的库

                                  我使用别名来引用 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:

                                  • file.h #include 的 experimental::filesystem> 并包含上面的代码
                                  • file.cpp,file.h的实现,#include的“file.h
                                  • ma​​in.cpp #include 的 filesystem> 和 "file.h"

                                  注意 main.cpp 和 file.h 中使用的不同的库。由于 main.cpp #include'd "file.h" 在 filesystem> 之后,使用的文件系统版本是 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中使用需要path_t给出“未定义的引用”错误,因为ma​​in.o 指的是 std::filesystem::path,但 file.o 指的是 std::experimental::filesystem::path

                                  分辨率

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

                                  【讨论】:

                                    【解决方案29】:

                                    链接共享库时,确保使用的符号没有隐藏。

                                    gcc 的默认行为是所有符号都是可见的。但是,当使用选项-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 
                                    

                                    隐藏/本地符号由 nm 以小写符号类型显示,例如 t 而不是 `T for code-section:

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

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

                                    与 Windows-dll 类似,可以使用定义标记公共函数,例如 DLL_PUBLIC 定义为:

                                    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
                                    
                                    DLL_PUBLIC int my_public_function(){
                                      ...
                                    }
                                    

                                    大致对应于Windows'/MSVC-version:

                                    #ifdef BUILDING_DLL
                                        #define DLL_PUBLIC __declspec(dllexport) 
                                    #else
                                        #define DLL_PUBLIC __declspec(dllimport) 
                                    #endif
                                    

                                    更多 information about visibility 可以在 gcc wiki 上找到。


                                    当使用-fvisibility=hidden 编译翻译单元时,生成的符号仍然具有外部链接(由nm 以大写符号类型显示)并且如果目标文件成为静态文件的一部分,则可以毫无问题地用于外部链接图书馆。仅当目标文件链接到共享库时,链接才成为本地链接。

                                    要查找目标文件中的哪些符号被隐藏运行:

                                    >>> objdump -t XXXX.o | grep hidden
                                    0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
                                    000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
                                    

                                    【讨论】:

                                      【解决方案30】:

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

                                      一个例子:-

                                      ma​​in.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.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
                                      

                                      如果编译器可以在每个可能调用gum 的源文件中看到它的定义,则只能将gum 定义为inline。这意味着它的内联定义需要存在于 header 文件中,您在每个源文件中 include 您编译其中可能会调用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.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
                                      

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

                                      【讨论】:

                                        最近更新 更多