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


1493

什么是未定义的参考/未解决的外部符号错误?常见原因是什么?如何解决/预防它们?

随意编辑/添加自己的。


@LuchianGrigore “随时添加答案”如果您愿意,我宁愿在主要答案中添加相关链接(IMHO)。
πάνταῥεῖ

19
这个问题太笼统,不能接受有用的答案。不相信这个问题,我简直不敢相信有1000多个声望很高的人对此发表评论。同时,我发现很多问题对我来说是明智的并且非常有用的。
艾伯特·

10
@AlbertvanderHorst“这个问题太笼统了,不能接受有用的答案。”下面的答案有用吗?
LF

4
它们可能很有用,就像标记为过于笼统的问题的许多答案一样。
艾伯特·凡德·霍斯特

1
老实说,我希望看到最小的可复制示例,这是我们对大多数新用户的要求。我的意思不是什么,只是-我们不能期望人们遵循我们不强加于自己的规则。
Danilo

Answers:


848

编译C ++程序分2.2(分给Keith Thompson供参考)指定,分几个步骤进行

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

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

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

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

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

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

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

常见的错误信息error LNK2001error LNK1120error LNK2019对于微软的Visual Studioundefined reference to 符号名称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

常见原因包括:


16
就个人而言,我认为MS链接器错误消息与GCC错误一样可读。它们还具有为未解决的外部名称同时包含修饰名称和未修饰名称的优点。当您需要直接查看库或目标文件以查看可能是什么问题(例如,调用约定不匹配)时,使用名称混乱的名称将很有帮助。另外,我不确定哪个版本的MSVC会在此处产生错误,但较新的版本包括引用了未解析的外部符号的函数名称(包括乱码和乱码)。
Michael Burr 2013年

5
David Drysdale写了一篇很棒的文章,介绍链接器的工作原理:入门指南。考虑到这个问题的主题,我认为它可能会有用。
Pressacco

@TankorSmash使用gcc吗?MinGW更精确。
doug65536

1
@luchian,如果您添加正确的代码,更正了上述错误,那就很好了
Phalgun

177

班级成员:

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()为pure:

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数据成员进行初始化。


只是想您可能想强调一下,两者都可以实现,而dtor实际上并不是例外。(乍看之下您的措辞并不明显。)
Deduplicator

112

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

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

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

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

libraryName只是库的简称,没有特定于平台的添加。因此,例如在Linux上,通常会调用文件,libfoo.so但是您只会写文件-lfoo。在Windows上,该文件可能称为foo.lib,但您将使用相同的参数。您可能需要添加目录,可以在其中找到这些文件-L‹directory›。确保不要在-l或后写空格-L

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

MSVS下,添加到项目中的lib文件会自动将其目标文件链接在一起,并且会生成一个文件(通常使用)。要在单独的项目中使用符号,您需要将lib文件包括在项目设置中。这是在项目属性的“链接器”部分的中完成的Input -> Additional Dependencies。(lib应在中添加文件的路径Linker -> General -> Additional Library Directories)使用随lib文件通常这样做会导致错误。

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

在Windows编程中,您没有链接必要库的迹象表明,未解析符号的名称以开头__imp_。在文档中查找该函数的名称,并应说明您需要使用哪个库。例如,MSDN将信息放在每个函数底部“方框”中的框中。


1
如果您能明确地掩盖gcc main.c而不是gcc main.c other.c (这是初学者通常在他们的项目变得如此之大以至于无法生成.o文件之前)的常见错误,那将是很好的。
MM

101

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

典型的变量声明是

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添加到源目录的头文件中的文件匹配的cpp文件也属于缺少定义的类别。
Laurie Stearn's

86

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

如果库相互依赖,则链接库的顺序确实很重要。通常,如果library A取决于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

因此,要再次重复,顺序确实很重要!


我很好奇的事实是,就我而言,我有一个依赖于共享库的目标文件。我不得不修改Makefile文件,并把库的物体用gcc 4.8.4在Debian。在带有gcc 4.4的Centos 6.5上,Makefile可以正常工作。
Marco Sulla

74

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

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

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

链接

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

并运行它

$ ./prog 
123456789

好的,我们破解它:)

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


71

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

5
或相反,如果您开发C库,一个不错的规则是通过用#ifdef __cplusplus [\n] extern"C" { [\n] #endif和包围所有导出的声明来保护头文件#ifdef __cplusplus [\n] } [\n] #endif[\n]是真正的回车符,但我不能在注释中正确地写上)。
Bentoy13年

就像上面的评论一样,此处的“创建混合语言标题”部分提供了帮助:oracle.com/technetwork/articles/servers-storage-dev/…–
zanbri

真的帮助了我!当我寻找这个问题时就是我的情况
knightofhorizo​​n

如果您偶然将普通的C ++头文件包含在extern C:中,也会发生这种情况extern "C" { #include <myCppHeader.h> }
ComFreek

68

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

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

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


1
正确-当库具有循环依赖性时,会发生这种情况。
Luchian Grigore 2013年

1
我扩展了您的答案,并链接到主要答案。谢谢。
Luchian Grigore 2013年

57

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

2
为了完整起见,此答案应提及GCC visibility和Windows的.def文件,因为它们也会影响符号名称和状态。
rubenvb 2012年

@rubenvb我已经很久没有使用.def文件了。随时添加答案或编辑答案。
Luchian Grigore

57

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

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

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

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

  • 编译时生成的所有目标文件
  • 被明确或隐式指定为该建筑应用程序的其他依赖关系的所有库(.lib)。

此搜索过程称为解析。

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

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

  1. 存在定义

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

int foo()
{
    return 0;
}

在b.cpp中,我们要调用函数foo,因此我们添加了

void foo();

声明函数foo(),并在另一个函数体中调用它,例如bar()

void bar()
{
    foo();
}

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

  1. 定义不存在

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


1
谢谢您的系统解释。在阅读完此答案后,我可以在这里理解其他答案。
displayName

55

模板实现不可见。

非专业模板的定义必须对使用它们的所有翻译单位可见。这意味着您无法将模板的定义与实现文件分开。如果必须分开实现,通常的解决方法是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移至头文件或使用该文件的翻译单元可见的位置。

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

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


1
有关“必须在标头中定义模板”的更多信息,请参见stackoverflow.com/questions/495021
PlasmaHH 2014年

41

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

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

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


以下是案例1案例2从更详细地处理现实世界的问题。


2
不禁会指出这个问题,这往往是由于没有主要功能而不是没有主要功能而引起的WinMain。有效的C ++程序需要一个main
克里斯

36

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


34

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

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

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


34

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

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

正确的解决方案是希望开发人员发布更新的程序包然后进行升级,但是它通过修改VS2013的一个额外设置(指向VS2012库文件)为我工作。

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


1
这似乎过于具体-也许一个新的线程将是解决此问题的更好的地方。
Luchian Grigore 2015年

3
@LuchianGrigore:我确实想在这里发帖因为那个问题恰好是这个问题,但是它被标记为这个问题的重复,所以我在那里无法回答。所以我在这里发布了答案。
恶意

该问题已经有一个可以接受的答案。由于上面列出了一般原因,因此将其标记为重复。如果我们对未包含库的每个问题都有答案,那会发生什么?
Luchian Grigore 2015年

6
@LuchianGrigore:此问题并非特定于库,它会影响使用Visual Studio程序包管理系统的所有库。我只是碰巧发现了另一个问题,因为我们俩都遇到了libpng问题。对于libxml2,libiconv和glew,我也遇到了相同的问题(使用相同的解决方案)。该问题与Visual Studio的软件包管理系统有关,我的回答解释了原因并提供了解决方法。有人看到“外部未解决”,并认为这实际上是一个程序包管理问题,它是一个标准链接程序问题。
Malvineous

34

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

编译的含义是什么?

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

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

链接项目的步骤

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

观察

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

如何解决这种错误

编译器时间错误:

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

链接器时间错误

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

您的答案不是专门针对Visual Studio吗?该问题未指定任何IDE /编译器工具,因此它使您的答案对于非Visual Studio而言毫无用处。
维克托·波莱沃

你是对的 。但是在每个IDE编译/链接过程中,每个过程都稍有不同。但是文件的处理过程完全相同(即使g ++在解析标志时也执行相同的操作。)

问题实际上与IDE无关,而与链接问题的答案有关。链接问题与IDE不相关,而与编译器和构建过程有关。
Victor Polevoy

是的。但是构建/链接过程是在g ++ / Visual Studio(Microsoft为VS提供的编译器)/ Eclipse / Net Beans中以相同的方式完成的

29

编译器/ IDE中的错误

我最近遇到了这个问题,事实证明这是Visual Studio Express 2013中的错误。我必须从项目中删除源文件,然后重新添加它以克服该错误。

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

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

5
认为您的工具已损坏很可能会使您远离真正的原因。您犯错的可能性比编译器引起问题的可能性要大得多。清理解决方案或重新创建构建配置可能会修复构建错误,但这并不意味着编译器中存在错误。链接“原来是错误”未经Microsoft确认,且不可复制。
JDiMatteo '02

4
@JDiMatteo这个问题上有21个答案,因此,大量的答案不会是“可能的”解决方案。如果您忽略所有低于可能性阈值的答案,那么此页面实际上将变得无用,因为无论如何大多数情况下都很容易发现。
developerbmw

27

使用链接器帮助诊断错误

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

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

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

对于MSVC,/VERBOSE(尤其是/VERBOSE:LIB)已添加到链接命令行。


26

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

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

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


25

由于涉及链接器错误时,人们似乎直接针对此问题,因此我将在此处添加它。

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文本文件:

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


20

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

  • 您正在尝试编译程序并将其与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");
}

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

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

$ 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.amy_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,链接序列完成;该引用未解决,并且链接失败。

最后,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或解析为/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':

也可以看看

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

将相互依存的库放错顺序只是获得需要定义的文件的一种方法,该文件需要在链接中晚于提供定义的文件。将库放在引用它们的目标文件之前是犯同样错误的另一种方法。


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)


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(我在示例中通过包含不同的路径使其变得明显,但可能并不总是那么明显。也许在编译器设置中include路径有所不同) 。

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

调试链接器

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

过程如下:

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

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

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


15

UNICODE定义不一致

一个Windows UNICODE构建是建立与TCHAR等被定义为wchar_t等。如果没有使用建筑UNICODE定义为构建与TCHAR定义char等,这些UNICODE_UNICODE定义影响到所有的T”字符串类型 ; LPTSTRLPCTSTR和他们的麋鹿。

建立一个图书馆 UNICODE定义的方法并尝试将其链接到未定义的项目中UNICODE会导致链接器错误,因为定义会不匹配TCHAR; charwchar_t

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

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

  1. 这可以通过以下任一方式完成:

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

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

  3. 或在命令行上;

    /DUNICODE /D_UNICODE

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

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


14

清理并重建

构建的“干净”可以清除以前的构建,失败的构建,不完整的构建以及其他与构建系统相关的构建问题可能留下的“死木”。

通常,IDE或内部版本将包含某种形式的“清除”功能,但可能未正确配置(例如,在手动makefile中)或可能失败(例如,中间或结果二进制文件为只读)。

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

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


10

在中缺少“ 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.cpp file1.cpp中

extern const int test;
extern int test2;

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


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的< 实验性::文件系统 >并包含上面的代码
  • file.cppfile.h的实现,#include的“ file.h
  • main.cpp #include的< 文件系统 >和“ file.h

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

$ g++ -g -std=c++17 -c main.cpp->将main.cpp编译为main.o
$ 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,因为给“未定义的引用”错误main.o简称std::filesystem::pathfile.ostd::experimental::filesystem::path

解析度

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


5

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

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

您应该使用nm -CDnm -gCD查看外部符号。另请参阅GCC Wiki上的可见性
jww

2

不同的架构

您可能会看到类似以下的消息:

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

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.