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

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

【问题讨论】:

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

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


【解决方案1】:

编译 C++ 程序分几个步骤进行,具体如下:2.2(credits to Keith Thompson for the reference):

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

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

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

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

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

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

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

    常见的错误消息是error 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
    

    和类似的错误微软视觉工作室:

    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>...	est2.exe : fatal error LNK1120: 4 unresolved externals
    

    常见原因包括:

【讨论】:

  • 就个人而言,我认为 MS 链接器错误消息与 GCC 错误一样可读。它们还有一个优势,即为未解析的外部对象包含了经过修饰的和未经过修饰的名称。当您需要直接查看库或目标文件以查看可能的问题(例如,调用约定不匹配)时,使用损坏的名称会很有帮助。另外,我不确定是哪个版本的 MSVC 在这里产生了错误,但是较新的版本包括引用未解析的外部符号的函数的名称(包括损坏的和未损坏的)。
  • 大卫德赖斯代尔写了一篇关于链接器如何工作的好文章: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 方法,增加的推理是 纯声明生成一个虚拟 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 数据成员进行初始化。

【讨论】:

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

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

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

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

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

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

libraryName 这里只是库的裸名,没有添加特定于平台的内容。所以例如在 Linux 上,库文件通常称为 libfoo.so,但您只写 -lfoo。在 Windows 上,同一文件可能称为 foo.lib,但您会使用相同的参数。您可能必须使用 -L‹directory› 添加可以找到这些文件的目录。请确保不要在-l-L 之后写一个空格。

为了X代码:添加用户标头搜索路径 -> 添加库搜索路径 -> 将实际库引用拖放到项目文件夹中。

在下面MSVS,添加到项目的文件会自动将它们的目标文件链接在一起,并且会生成一个 lib 文件(通常使用)。要在单独的项目中使用这些符号,您需要 需要在项目设置中包含 lib 文件。这是在项目属性的链接器部分完成的,位于Input -> Additional Dependencies。 (lib 文件的路径应该是 添加于Linker -> General -> Additional Library Directories) 当使用lib文件提供的第三方库时,不这样做通常会导致错误。

也可能是您忘记将文件添加到编译中,在这种情况下不会生成目标文件。在海合会您会将文件添加到命令行。在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 并放入库Debian 上 gcc 4.8.4 的对象。在带有 gcc 4.4 的 Centos 6.5 上,Makefile 可以正常工作。
【解决方案6】:

符号在 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 [ ] extern"C" { [ ] #endif#ifdef __cplusplus [ ] } [ ] #endif 包围所有导出的声明来保护头文件([ ] 是真正的回车,但我不能写这个在评论中正确)。
  • 正如上面的评论,这里的“创建混合语言标题”部分有所帮助:oracle.com/technetwork/articles/servers-storage-dev/…
  • 如果您不小心包含了普通的 C++ 头文件,也会发生这种情况外部 Cextern "C" { #include <myCppHeader.h> }
【解决方案7】:

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

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

注意:我使用 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
", global_var_name, local_var_name);
    printf("%d
", 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
", 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 具有 Bind GLOBAL(原为本地)

关联

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

并运行它

$ ./prog 
123456789

好的,我们破解它:)

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

【讨论】:

    【解决方案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?如果链接器找不到内部没有定义的符号的外部定义,它会报告未解析的外部符号错误。

        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. 定义不存在

          如果我们想调用一个库中的一些函数,但是导入库没有添加到你项目设置的附加依赖列表(set from: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 或类似内容的未定义引用'异常'main()入口点参考(特别是)。

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

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


          这里是case1case2从一个更详细的处理真实世界问题。

          【讨论】:

          • 忍不住要指出this question,事实上,与没有WinMain相比,根本没有主要功能更容易造成这种情况。有效的 C++ 程序需要 main
          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

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

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

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

            【讨论】:

              【解决方案15】:

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

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

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

              我通过查找 packagenameuild ativepackagename.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 将输出写入一个名为项目名称.Cpp.Clean.txt其中包含必须由链接器处理的所有目标文件。

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

              链接项目的步骤

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

              观察

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

                如何解决这种错误

                编译器时间错误:

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

                链接器时间错误

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

              【讨论】:

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

              使用链接器帮助诊断错误

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

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

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

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

              【讨论】:

                【解决方案18】:

                编译器/IDE 中的错误

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

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

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

                【讨论】:

                • 认为你的工具坏了很可能会让你远离真正的原因。与编译器导致问题相比,您犯错误的可能性要大得多。清理您的解决方案或重新创建您的构建配置可能会修复构建错误,但这并不意味着编译器中存在错误。链接的“事实证明这是一个错误”未经 Microsoft 确认且不可重现。
                • @JDiMatteo 这个问题有 21 个答案,因此大量答案不会成为“可能”的解决方案。如果您拒绝所有低于您的可能性阈值的答案,那么这个页面实际上变得毫无用处,因为大多数常见情况无论如何都很容易被发现。
                【解决方案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某物错误。
                    • 但是所有未定义的某物s 在你的头文件中声明 #included 实际上是在您链接的库中定义的。

                    示例在 C 中。它们同样可以是 C++

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

                    my_lib.c

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

                    我的库.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
                    ",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.a,它想知道你的程序是否 需要libmy_lib.a 中的任何目标文件。

                    libmy_lib.a里面只有一个object文件,即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时,有对任何事物的引用 节目中:都在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
                        }
                        

                        上面的代码将operator的friendship限制在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(...);
                          }
                          

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

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

                          调试链接器

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

                          这个过程是这样的:

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

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

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

                          【讨论】:

                            【解决方案25】:

                            不一致的UNICODE定义

                            Windows UNICODE 构建是使用TCHAR构建的,被定义为wchar_t等。当不使用UNICODE构建时,定义为使用TCHAR构建,定义为char等。这些UNICODE_UNICODE定义影响所有"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 中文件1.cpp

                                extern const int test;
                                extern int test2;
                                

                                或者,可以在 file1.cpp 中使用显式 extern 声明 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:

                                  • 文件.h#include 的 <实验::文件系统> 并包含上面的代码
                                  • 文件.cpp,执行file.h,#include的“文件.h
                                  • 主.cpp#include 的 <文件系统> 和“文件.h

                                  注意不同的图书馆在 main.cpp 和 file.h 中使用。由于 main.cpp #include'd “文件.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 中使用需要path_t给出了“未定义的参考”错误,因为主程序提到std::filesystem::path文件.ostd::experimental::filesystem::path.

                                  解析度

                                  要解决这个问题,我只需要将 file.h 中的 <experimental::filesystem> 更改为 <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 用于代码部分:

                                    nm XXX.so
                                    00000000000005a7 t HIDDEN_SYMBOL
                                    00000000000005f8 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
                                    

                                    更多 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 说明符的源文件中定义。

                                      一个例子:-

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

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

                                      口香糖.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。这意味着它的内联定义需要存在于标头归档你包括在每个源文件中 您编译其中可能会调用gum。做以下两件事之一:

                                      要么不内联定义

                                      从源文件定义中删除 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
                                      

                                      口香糖.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
                                      

                                      【讨论】:

                                      • 值得注意的是,如果您将定义放在标题中但是忽略内联说明符你得到相反的错误:复制符号 ;-)。有趣的是,inline 唯一保证的效果是它使定义文件成为静态的。 (特别是,inline 不保证实际内联任何内容 ;-)。)
                                      猜你喜欢
                                      相关资源
                                      最近更新 更多