1686

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

4

37 回答 37

940

按照2.2 的规定,编译 C++ 程序需要几个步骤(参考 Keith Thompson)

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

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

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

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

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

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

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

常见的错误消息是Microsoft Visual Studioerror LNK2001,和GCCsymbolNameerror LNK1120error LNK2019undefined reference to

编码:

struct X
{
   virtual void foo();
};
struct Y : X
{
   void foo() {}
};
struct A
{
   virtual ~A() = 0;
};
struct B: A
{
   virtual ~B(){}
};
extern int x;
void foo();
int main()
{
   x = 0;
   foo();
   Y y;
   B b;
}

将使用GCC生成以下错误:

/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status

以及Microsoft Visual Studio的类似错误:

1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals

常见原因包括:

于 2012-09-24T22:27:40.680 回答
190

Class members:

A pure virtual destructor needs an implementation.

Declaring a destructor pure still requires you to define it (unlike a regular function):

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

This happens because base class destructors are called when the object is destroyed implicitly, so a definition is required.

virtual methods must either be implemented or defined as pure.

This is similar to non-virtual methods with no definition, with the added reasoning that the pure declaration generates a dummy vtable and you might get the linker error without using the function:

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

For this to work, declare X::foo() as pure:

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

Non-virtual class members

Some members need to be defined even if not used explicitly:

struct A
{ 
    ~A();
};

The following would yield the error:

A a;      //destructor undefined

The implementation can be inline, in the class definition itself:

struct A
{ 
    ~A() {}
};

or outside:

A::~A() {}

If the implementation is outside the class definition, but in a header, the methods have to be marked as inline to prevent a multiple definition.

All used member methods need to be defined if used.

A common mistake is forgetting to qualify the name:

struct A
{
   void foo();
};

void foo() {}

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

The definition should be

void A::foo() {}

static data members must be defined outside the class in a single translation unit:

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

An initializer can be provided for a static const data member of integral or enumeration type within the class definition; however, odr-use of this member will still require a namespace scope definition as described above. C++11 allows initialization inside the class for all static const data members.

于 2012-09-24T23:38:20.420 回答
129
于 2012-09-24T23:37:30.810 回答
110

Declared but did not define a variable or function.

A typical variable declaration is

extern int x;

As this is only a declaration, a single definition is needed. A corresponding definition would be:

int x;

For example, the following would generate an error:

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

Similar remarks apply to functions. Declaring a function without defining it leads to the error:

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

Be careful that the function you implement exactly matches the one you declared. For example, you may have mismatched cv-qualifiers:

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)
                          

Other examples of mismatches include

  • Function/variable declared in one namespace, defined in another.
  • Function/variable declared as class member, defined as global (or vice versa).
  • Function return type, parameter number and types, and calling convention do not all exactly agree.

The error message from the compiler will often give you the full declaration of the variable or function that was declared but never defined. Compare it closely to the definition you provided. Make sure every detail matches.

于 2012-09-24T23:38:00.483 回答
92

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

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

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

于 2014-07-10T11:46:08.707 回答
78

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

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

注意:我使用 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 有 Bind GLOBAL (是 LOCAL)

关联

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

并运行它

$ ./prog 
123456789

好的,我们破解它:)

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

于 2014-10-07T10:09:10.750 回答
76

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

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

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

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

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

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

extern "C" void foo();

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

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

extern "C" {
    #include "cheader.h"
}
于 2012-09-24T23:39:15.303 回答
72

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

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

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

于 2013-12-03T18:11:27.867 回答
63

Template implementations not visible.

Unspecialized templates must have their definitions visible to all translation units that use them. That means you can't separate the definition of a template to an implementation file. If you must separate the implementation, the usual workaround is to have an impl file which you include at the end of the header that declares the template. A common situation is:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

To fix this, you must move the definition of X::foo to the header file or some place visible to the translation unit that uses it.

Specialized templates can be implemented in an implementation file and the implementation doesn't have to be visible, but the specialization must be previously declared.

For further explanation and another possible solution (explicit instantiation) see this question and answer.

于 2012-09-24T23:38:55.287 回答
63

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

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

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

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

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

这个搜索过程称为resolve。

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

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

于 2015-04-13T16:42:19.767 回答
61

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

__declspec(dllexport)MSVS 要求您使用和指定要导出和导入的符号__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
{
};
于 2012-09-24T23:39:29.873 回答
44

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

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

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


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

于 2014-03-03T23:52:27.623 回答
38

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

于 2014-06-18T17:06:24.813 回答
35

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

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

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

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

于 2015-01-17T02:24:04.753 回答
35

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

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

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

于 2014-09-09T12:09:01.367 回答
35

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

编译是什么意思?

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

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

链接项目的步骤

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

观察

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

如何解决此类错误

编译器时间错误:

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

链接器时间错误

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

编译器/IDE 中的错误

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

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

  • 清理项目(某些 IDE 可以选择执行此操作,您也可以通过删除目标文件手动执行此操作)
  • 尝试开始一个新项目,从原始项目中复制所有源代码。
于 2015-01-02T22:06:08.993 回答
30

使用链接器帮助诊断错误

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

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

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

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

于 2015-07-27T10:20:40.337 回答
26

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

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

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

于 2014-04-04T15:02:33.350 回答
25

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

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

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

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

于 2015-09-10T11:03:35.357 回答
23

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

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

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

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

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

你建立你的静态库:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

你编译你的程序:

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

您尝试将其链接libmy_lib.a并失败:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

如果您一步编译和链接,结果相同,例如:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

一个涉及共享系统库的最小示例,压缩库libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

编译你的程序:

$ gcc -c -o eg2.o eg2.c

尝试链接您的程序libz并失败:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

如果您一次性编译和链接,则相同:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

示例 2 的变体包括pkg-config

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

你在做什么错?

在您要链接以制作程序的目标文件和库的序列中,您将库放在引用它们的目标文件之前。您需要将库放在引用它们的目标文件之后。

正确链接示例 1:

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

成功:

$ ./eg1 
Hello World

正确链接示例 2:

$ gcc -o eg2 eg2.o -lz

成功:

$ ./eg2 
1.2.8

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

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

说明

从这里开始阅读是可选的

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

首先,示例 1,带有静态库my_lib.a

静态库是目标文件的索引存档。当链接器-lmy_lib在链接序列中找到并发现 this 指的是静态库./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对最终的引用unresolved,并且链接失败。

二、示例2,带共享库libz

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

如今,许多 Linux 发行版配置其 GCC 工具链,以便其语言驱动程序(gccg++gfortran)指示系统链接器( )根据需要ld链接共享库。您拥有其中一个发行版。

这意味着当链接器-lz在链接序列中找到并发现 this 指的是共享库 (say)/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,则链接序列完成;该引用未解决,并且链接失败。

最后,pkg-config示例 2 的变体现在有了明显的解释。外壳扩展后:

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或 to /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 在 compile + link 情况下生成了一个临时目标文件,因为您没有告诉它使用eg1.o. 我们看:

/tmp/ccQk1tvs.o: In function `main'

代替:

eg1.o: In function `main':

也可以看看

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

将相互依赖的库以错误的顺序放置只是一种方式,您可以通过这种方式获取需要在链接中稍后出现的事物定义的文件,而不是提供定义的文件。将库放在引用它们的目标文件之前是犯同样错误的另一种方法。

于 2017-04-09T10:34:09.087 回答
20

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

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

INPUT (libtbb.so.2)

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

cp libtbb.so.2 libtbb.so

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

于 2015-03-30T16:03:16.230 回答
18

与模板成为朋友...

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

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

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

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

由于它没有实现,链接器找不到它并导致错误。

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

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

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

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

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

替代方案包括;

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

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

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

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

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

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

显示上述技术的代码清单


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

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

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

于 2016-03-09T12:08:26.797 回答
16

当您的包含路径不同时

当头文件及其关联的共享库(.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:重写第一部分以便于理解。请在下面发表评论,让我知道是否需要修复其他问题。谢谢!

于 2017-03-03T06:57:30.017 回答
15

不一致的UNICODE定义

Windows UNICODE 构建是使用TCHARetc. 定义为wchar_tetc 构建的。当不构建UNICODE定义为构建时TCHAR定义为charetc。这些UNICODE_UNICODE定义影响所有T”字符串类型LPTSTRLPCTSTR还有他们的麋鹿。

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

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

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

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

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

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

  3. 或者在命令行上;

    /DUNICODE /D_UNICODE
    

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

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

于 2016-04-07T11:53:38.320 回答
14

清理和重建

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

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

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

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

于 2016-02-24T10:40:51.590 回答
11

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

对于来自 C 的人来说,在 C++ 中全局const变量具有内部(或静态)链接可能会令人惊讶。在 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.cppfile1.cpp

extern const int test;
extern int test2;

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

于 2017-08-03T08:01:49.237 回答
8

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

不同版本的库

我使用别名来引用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
  • main.cpp #include的<文件系统>和“ 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 中使用的任何需要的函数都会给出“未定义的引用”错误,因为main.o引用但file.o指向. path_tstd::filesystem::pathstd::experimental::filesystem::path

解析度

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

于 2018-08-27T16:43:54.680 回答
7

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

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

您可以通过调用来检查您正在寻找的符号是否是外部的:

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

隐藏/局部符号nm以小写符号类型显示,例如,t而不是 `T 用于代码段:

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 版本:

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

有关可见性的更多信息可以在 gcc wiki 上找到。


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

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

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
于 2018-09-07T06:22:40.167 回答
4

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

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

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

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

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

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

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

输出:

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

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

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

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

输出:

hello, world!!

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

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

考虑以下 C++ 程序。这里我们调用了func1()。它的原型是int func1()。但其定义与其原型不符。如我们所见,函数的定义包含函数的参数。

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

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

输出:

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

因此,为了防止此类错误,我们只需交叉检查所有对象的定义和用法在我们的程序中是否匹配。

#3) 目标文件未正确链接

此问题还可能导致“未定义引用”错误。在这里,我们可能有多个源文件,我们可能会独立编译它们。完成此操作后,对象未正确链接,并导致“未定义引用”。

考虑以下两个 C++ 程序。在第一个文件中,我们使用了在第二个文件中定义的“print()”函数。当我们分别编译这些文件时,第一个文件为 print 函数提供了“未定义的引用”,而第二个文件为 main 函数提供了“未定义的引用”。

int print();
int main()
{
    print();
}

输出:

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

int print() {
    return 42;
}

输出:

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

解决此错误的方法是同时编译这两个文件(例如,使用 g++)。

除了已经讨论过的原因之外,“未定义的引用”也可能由于以下原因而发生。

#4) 错误的项目类型

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

#5) 没有图书馆

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

#6) 相关文件未编译

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

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

于 2020-10-22T09:16:56.423 回答
4

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

一个例子:-

主文件

#include "gum.h"
#include "foo.h"

int main()
{
    gum();
    foo f;
    f.bar();
    return 0;
}

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

  • 不发出 的任何唯一定义gum,因此
  • 不发出链接器可以引用的任何符号gum,而是
  • 将所有调用替换gumgum.

因此,如果您gum在源文件中定义 inline gum.cpp,它将被编译为一个目标文件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好像编译器可以在每个可能被调用inline的源文件中看到它的定义。gum这意味着它的内联定义需要存在于您编译每个可能被调用的源文件中的文件中。做以下两件事之一: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

成功。

或正确内联

头文件中的内联定义:

富.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.cppor gum.cpp

$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const
于 2019-01-21T19:44:00.697 回答
3

需要考虑的一些拼写错误:(作为初学者经常发生在我身上)

  • 如果您正在使用一个类:检查您是否在定义函数的 cpp 文件中的函数名之前忘记了“classname::”。
  • 如果您使用前向声明:请务必声明正确的类型。例如:如果你想转发声明一个“结构”使用“结构”而不是“类”。
于 2021-10-18T14:50:21.403 回答
2

不同的架构

您可能会看到如下消息:

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

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

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

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

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

于 2018-12-10T16:40:04.397 回答
2

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

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

$ gcc -o test test.cpp    
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: warning: relocation against `_ZSt4cout' in read-only section `.text' 
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: in function `main': test.cpp:(.text+0xe): undefined reference to `std::cout' 
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: test.cpp:(.text+0x13): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)' 
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: in function `__static_initialization_and_destruction_0(int, int)': 
test.cpp:(.text+0x43): undefined reference to `std::ios_base::Init::Init()' 
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: test.cpp:(.text+0x58): undefined reference to `std::ios_base::Init::~Init()' 
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: warning: creating DT_TEXTREL in a PIE 
collect2: error: ld returned 1 exit status
于 2021-04-23T20:49:52.563 回答
0

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

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

//class A header file 
class ClassB; // FORWARD DECLERATION
class ClassA
{
public:
ClassB* bObj;
ClassA(HINSTANCE hDLL) ;
//  member functions
}
--------------------------------
//class A cpp file   
ClassA::ClassA(HINSTANCE hDLL) 
{
    bObj = new ClassB();// LNK2019 occures here 
    bObj ->somefunction();// LNK2019 occures here
}
/*************************/

//classB Header file
struct mystruct{}
class ClassB{
public:
ClassB();
mystruct somefunction();
}

------------------------------
//classB cpp file  
/* This is the file with the wrong property item type in visual studio --C/C++ Header-*/
ClassB::somefunction(){}
ClassB::ClassB(){}
于 2021-02-12T08:42:47.000 回答
0

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

int createBackground(VertexArray rVA, IntRect arena);

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

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

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

干杯。

于 2020-02-28T16:46:49.970 回答
0

我的例子:

头文件

const string GMCHARACTER("character");
class GameCharacter : public GamePart
{
    private:
        string name;
        static vector<GameCharacter*> characterList;
    public:
        GameCharacter(cstring nm, cstring id) :
            GamePart(GMCHARACTER, id, TRUE, TRUE, TRUE),
            name(nm)
            { }
...
}

.cpp 文件:

vector<GameCharacter*> characterList;
...

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

我添加这个是因为——虽然其他人将这个案例列在一长串需要注意的事项中——但该列表没有给出示例。这是一个需要寻找更多东西的例子,尤其是在 C++ 中。

于 2020-05-31T06:03:33.197 回答