提问



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


随意编辑/添加自己的。

最佳参考


编译C ++程序分为几个步骤,由 2.2 指定(以Keith Thompson为参考):



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

  
  

      
  1. 物理源文件字符以实现定义的方式映射到基本源字符集
      (如果,引入行尾指标的换行符)
      必要。 [[SNIP]]

  2.   
  3. 删除反斜杠字符(\\)后面紧跟一个新行字符的每个实例,将物理源代码行拼接到
      形成逻辑源代码行。 [[SNIP]]

  4.   
  5. 源文件被分解为预处理标记(2.5)和空白字符序列(包括注释)。 [[SNIP]]

  6.   
  7. 执行预处理指令,扩展宏调用,并执行_Pragma一元运算符表达式。 [[SNIP]]

  8.   
  9. 字符文字或字符串文字中的每个源字符集成员,以及每个转义序列和通用字符名称
      在字符文字或非原始字符串文字中,转换为
      执行字符集的相应成员; [[SNIP]]

  10.   
  11. 连接相邻的字符串文字标记。

  12.   
  13. 分隔标记的空白字符不再重要。每个预处理令牌都转换为令牌。 (2.7)。该
      得到的标记在语法和语义上进行分析
      翻译为翻译单位。 [[SNIP]]

  14.   
  15. 翻译的翻译单元和实例化单元组合如下: [[SNIP]]

  16.   
  17. 解析所有外部实体引用。库组件被链接以满足对未定义的实体的外部引用
      目前的翻译。所有这些翻译器输出都被收集到一个
      程序映像,包含在其中执行所需的信息
      执行环境。
    (强调我的)

  18.   

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



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


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


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


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


Microsoft Visual Studio [[和undefined reference to symbolName 的常见错误消息为error LNK2001error LNK1120error LNK2019 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


常见原因包括:



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

  • 声明和未定义的变量或函数。

  • 班级成员的常见问题

  • 模板实现不可见。

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

  • 跨模块/dll错误地导入/导出方法/类。 (特定MSVS)

  • 循环库依赖

  • 未定义引用WinMain @ 16

  • 相互依存的图书馆订单

  • 多个同名的源文件

  • 使用#pragma(Microsoft Visual Studio)时出现错误或不包含.lib扩展名

  • 模板朋友的问题

  • UNICODE定义不一致


其它参考1


班级成员:



virtual析构函数需要实现。



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


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-use仍然需要如上所述的命名空间范围定义。 C ++ 11允许在类中为所有static const数据成员进行初始化。

其它参考2


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



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


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


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


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


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


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


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


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

其它参考3


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



典型的变量声明是


extern int x;


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


int x;


例如,以下内容将生成错误:


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


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


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


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


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


其他不匹配的例子包括



  • 在一个名称空间中声明的函数/变量,在另一个名称空间中定义。

  • 声明为类成员的函数/变量,定义为全局(反之亦然)。

  • 函数返回类型,参数编号和类型以及调用约定并不完全一致。



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

其它参考4


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



库链接的顺序如果库彼此依赖则很重要。通常,如果库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


所以再次重复,订单 DOES 很重要!

其它参考5


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


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



  注意:我使用g ++和Linux,所有的例子都是为了它



例如,我们有一些代码


// src1.cpp
void print();

static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;

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





// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
//extern int local_var_name;

void print ()
{
    // printf("%d%d\n", global_var_name, local_var_name);
    printf("%d\n", global_var_name);
}


制作目标文件


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


在汇编程序阶段之后,我们有一个目标文件,其中包含要导出的任何符号。
看看符号


$ readelf --symbols src1.o
  Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL14local_var_name # [1]
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_var_name     # [2]


我已经拒绝了输出中的一些行,因为它们并不重要


因此,我们看到要导出的符号。


[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable


src2.cpp什么都没有输出,我们没有看到它的符号


链接我们的目标文件


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


并运行它


$ ./prog
123


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


// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
extern int local_var_name;

void print ()
{
    printf("%d%d\n", global_var_name, local_var_name);
}


并重建目标文件


$ g++ -c src2.cpp -o src2.o


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


$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status


之所以发生这种情况是因为我们的local_var_name是静态的,即它对其他模块不可见。
现在更深刻了。获取转换阶段输出


$ g++ -S src1.cpp -o src1.s

// src1.s
look src1.s

    .file   "src1.cpp"
    .local  _ZL14local_var_name
    .comm   _ZL14local_var_name,4,4
    .globl  global_var_name
    .data
    .align 4
    .type   global_var_name, @object
    .size   global_var_name, 4
global_var_name:
    .long   123
    .text
    .globl  main
    .type   main, @function
main:
; assembler code, not interesting for us
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits


所以,我们已经看到local_var_name没有标签,这就是为什么链接器没有找到它。但我们是黑客:)我们可以修复它。在文本编辑器中打开src1.s并更改


.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4





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


即你应该如下


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


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


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


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


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


现在local_var_name有绑定GLOBAL(是LOCAL)


链接


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


并运行它


$ ./prog 
123456789


好的,我们破解它:)


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

其它参考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"
}

其它参考7


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


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


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

其它参考8


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

其它参考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的定义移动到头文件或使用它的翻译单元可见的某个位置。


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


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

其它参考10


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


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


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


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



  • 在编译时生成的所有目标文件

  • 显式或隐式的所有库(.lib)
    指定为此建筑应用程序的附加依赖项。



此搜索过程称为resolve。


d。最后,为什么没有解析外部符号?
如果链接器无法在内部找到没有定义的符号的外部定义,则会报告未解析的外部符号错误。


电子。 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


未定义参考WinMain@16或类似的不寻常 main()入口点参考(特别是对于visual-studio)。/questions/tagged/visual-studio


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


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





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

其它参考12


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

其它参考13


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


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


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

其它参考14


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


我只是试图将libpng链接到Visual Studio 2013时遇到此问题。问题是包文件只有Visual Studio 2010和2012的库。


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


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

其它参考15


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


编译的含义是什么?



  • Visual Studio搜索文件 .vcxproj 并开始编译扩展名为.cpp的每个文件。编译顺序未定义。因此,您不能假设首先编译main.cpp文件

  • 如果.cpp文件依赖于其他.h文件以查找符号
    可能会也可能不会在文件.cpp
  • 中定义
  • 如果存在一个.cpp文件,其中编译器找不到一个符号,编译器时间错误会引发消息无法找到符号x

  • 对于每个扩展名为.cpp的文件,都会生成一个目标文件.o,并且Visual Studio会将输出写入名为 ProjectName.Cpp.Clean.txt 的文件中,该文件包含必须存在的所有目标文件由链接器处理。



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


关联项目的步骤



  • 解析所有目标文件并找到仅在头文件中声明的定义(例如:前面答案中提到的类的一个方法的代码,或者事件初始化静态变量,它是类中的成员)

  • 如果在目标文件中找不到一个符号,他也会在附加库中搜索。用于向项目添加新库配置属性 - > VC ++目录 - > 图书馆目录,此处您指定了用于搜索库和配置属性 - > 链接器 - > 输入的其他文件夹指定库的名称。
    - 如果链接器找不到您在一个.cpp中写入的符号,则会引发链接器时间错误,这可能听起来像
    error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)



观察



  1. 一旦链接器找到一个符号,他就不会在其他库中搜索它

  2. 链接库的顺序非常重要

  3. 如果链接器在一个静态库中找到外部符号,则在项目的输出中包含该符号。但是,如果库是共享的(动态的),则他不会在输出中包含代码(符号),而是可能会发生运行时崩溃



如何解决此类错误


编译器时间错误:



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



链接器时间错误



  • 定义您在头文件中声明的所有符号

  • 使用#pragma once允许编译器不包含一个标头(如果它已经包含在编译的当前.cpp中)

  • 确保您的外部库不包含可能与您在标题文件中定义的其他符号冲突的符号

  • 使用模板时,请确保在头文件中包含每个模板函数的定义,以允许编译器为任何实例化生成适当的代码。


其它参考16


编译器/IDE中的错误



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


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



  • 清理项目(某些IDE可以选择执行此操作,您也可以
    通过删除目标文件来手动完成)

  • 尝试开始一个新项目,
    复制原始源代码。


其它参考17


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


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


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

其它参考18


使用链接器帮助诊断错误



大多数现代连接器都包含一个不同程度打印的冗长选项;



  • 链接调用(命令行),

  • 关于链接阶段包含哪些库的数据,

  • 图书馆的位置,

  • 搜索使用的路径。



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



  • Linux ld手册页。

  • LLVM链接器页面。

  • 海湾合作委员会简介第9章。



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



  • /VERBOSE链接器选项上的MSDN页面。


其它参考19


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


使用GCC 5.2.0发生链接器错误的一个可能原因是,现在默认选择新的libstdc ++库ABI。



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



因此,如果您在5.1.0之后切换到GCC时突然遇到链接器错误,那么这将是一个要检查的事情。

其它参考20


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


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


INPUT (libtbb.so.2)


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


cp libtbb.so.2 libtbb.so


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

其它参考21


交友模板......



给出带有友元操作符(或函数)的模板类型的代码片段;


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


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


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


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


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


// forward declare the 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; [306]



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



在cppreference和C ++ FAQ上有关于模板朋友的进一步阅读。[307] [308]


显示上述技术的代码清单。[309]





作为失败代码示例的旁注; 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)


其它参考22


UNICODE定义不一致



Windows TCHAR等构建的Windows UNICODE构建被定义为wchar_t等。当不用UNICODE构建时,TCHAR定义为char等定义为构建这些UNICODE​​]]和_UNICODE定义会影响所有T字符串类型; LPTSTRLPCTSTR和他们的麋鹿。[310]


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


该错误通常包括具有charwchar_t派生类型的值的函数,这些函数也可包括std::basic_string<>等。浏览代码中受影响的函数时,通常会引用TCHARstd::basic_string<TCHAR>等。这是一个告诉标志,该代码最初用于UNICODE和Multi -Byte Character(或narrow)构建。


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



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


    #define UNICODE
    #define _UNICODE
    

  2. 或者在项目设置中;



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


  3. 或在命令行上;


    /DUNICODE /D_UNICODE
    



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


不要忘记在Release和Debug构建之间保持一致。

其它参考23


清理并重建



构建的干净可以消除可能遗留在先前构建,失败构建,不完整构建和其他构建系统相关构建问题的死木。


通常,IDE或构建将包括某种形式的干净功能,但这可能没有正确配置(例如在手动makefile中)或者可能失败(例如,中间或结果二进制文件是只读的)。


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


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

其它参考24


您的链接在引用它们的对象文件之前使用库




  • 您正在尝试使用GCC工具链编译和链接您的程序。

  • 您的链接指定了所有必要的库和库搜索路径

  • 如果libfoo取决于libbar,那么您的链接正确地将libfoo放在libbar之前。

  • 您的链接因undefined reference to 某些错误而失败。

  • 但所有未定义的某些都是在您拥有的头文件中声明的
    #include d并且实际上是在您链接的库中定义的。



示例在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在您的发行版上生成的链接命令,
从左到右使用链接中的文件
命令行序列。当它发现文件引用某事时
并且不包含它的定义,以搜索定义
在右边的文件中。如果它最终找到一个定义,那么
参考已解决。如果任何引用仍未解决,
链接失败:链接器不向后搜索。


首先,示例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变体现在有了明显的解释。
shell扩展后:


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中解释的 as-needed 基础上,但共享库是无条件链接的。


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



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

  • 静态和共享库的不同链接规则可能会令人困惑
    不熟练的程序员,他们可能不知道-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


因此,如果您执行运行这两个命令,链接就会失败。该
只有差异y你注意到失败的是gcc已经产生了一个
编译+链接案例中的临时目标文件,因为你没有告诉它
使用eg1.o。我们看:


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


代替:


eg1.o: In function `main':


另见



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


将相互依赖的库置于错误的顺序只是一种方式
您可以在其中获取需要即将发生的事情定义的文件
后来在链接中比提供定义的文件。把图书放在。之前
引用它们的目标文件是另一种造成同样错误的方法。

其它参考25


当您的包含路径不同时



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


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


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


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

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


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


你可能会问这样的情况会怎样结束? 包含路径当然!如果在编译共享库时,包含路径会导致header1.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在两个项目中都是不同的类型。这可能是因为它们来自两个略有不同的包含文件(也许包含文件来自两个不同版本的库)。


调试链接器



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


过程如下:



  1. 请注意链接器错误中给出的奇怪的错位名称。 (例如,绘制@ graphics @ XYZ)。

  2. 将导出的符号从库中转储到文本文件中。

  3. 搜索感兴趣的导出符号,并注意已损坏的名称不同。

  4. 注意为什么受损的名字最终会有所不同。您将能够看到参数类型不同,即使它们在源代码中看起来相同。

  5. 他们与众不同的原因。在上面给出的示例中,由于包含不同的文件,它们是不同的。



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;
}
项目是指一组源文件,它们链接在一起以生成库或可执行文件。


编辑1:重写第一部分更容易理解。如果需要修复其他内容,请在下方发表评论以告诉我。谢谢!

其它参考26


const变量声明/定义中缺少extern(仅限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.cpp 和 file1.cpp中


extern const int test;
extern int test2;


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