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

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

随意编辑/添加您自己的 .

回答(30)

3 years ago

编译C程序分为几个步骤,由 2.2 (credits to Keith Thompson for the reference)指定:

翻译语法规则的优先级由以下阶段指定[见脚注] . 如果需要,物理源文件字符以实现定义的方式映射到基本源字符集(引入行尾指示符的换行符) . [SNIP]删除反斜杠字符(\)后面紧跟一个新行字符的每个实例,拼接物理源代码行以形成逻辑源代码行 . [SNIP]源文件被分解为预处理标记(2.5)和空白字符序列(包括注释) . [SNIP]执行预处理指令,扩展宏调用,并执行_Pragma一元运算符表达式 . [SNIP]字符文字或字符串文字中的每个源字符集成员,以及字符文字或非原始字符串文字中的每个转义序列和通用字符名称,都将转换为执行的相应成员字符集; [SNIP]连接相邻的字符串文字标记 . 分隔标记的空白字符不再重要 . 每个预处理令牌都转换为令牌 . (2.7) . 由此产生的标记在语法和语义上进行分析并翻译为翻译单元 . [SNIP]翻译的翻译单元和实例化单元组合如下:[SNIP]解析所有外部实体参考 . 链接库组件以满足对当前转换中未定义的实体的外部引用 . 所有这样的翻译器输出被收集到程序映像中,该程序映像包含在其执行环境中执行所需的信息 . (强调我的)[脚注]实现必须表现得好像这些单独的阶段发生,尽管在实践中不同的阶段可能会折叠在一起 .

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

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

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

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

对于 Microsoft Visual Studio ,常见错误消息为 error LNK2001error LNK1120error LNK2019GCCundefined reference to symbolName .

代码:

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

常见原因包括:

3 years ago

class 成员:

纯虚析构函数需要实现 .

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

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

发生这种情况是因为在隐式销毁对象时会调用基类析构函数,因此需要定义 .

必须实现

虚方法或将其定义为纯方法 .

这与没有定义的非 virtual 方法类似,添加的理由是纯声明生成虚拟vtable,并且您可能在不使用该函数的情况下获得链接器错误:

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

为此,将 X::foo() 声明为纯:

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

非虚拟类成员

即使未明确使用,也需要定义某些成员:

struct A
{ 
    ~A();
};

以下将产生错误:

A a;      //destructor undefined

在类定义本身中,实现可以是内联的:

struct A
{ 
    ~A() {}
};

或外面:

A::~A() {}

如果实现在类定义之外,但在 Headers 中,则必须将方法标记为 inline 以防止多重定义 .

如果使用,则需要定义所有使用的成员方法 .

一个常见的错误就是忘记取名这个名字:

struct A
{
   void foo();
};

void foo() {}

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

定义应该是

void A::foo() {}

必须在单个翻译单元中的类外定义

静态数据成员:

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

可以为类定义中的整数或枚举类型的 static const 数据成员提供初始值设定项;但是,此成员的odr使用仍将需要如上所述的命名空间范围定义 . C 11允许在类中为所有 static const 数据成员进行初始化 .

3 years ago

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

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

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

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

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

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

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

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

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

3 years ago

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

典型的变量声明是

extern int x;

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

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)

其他不匹配的例子包括

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

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

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

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

3 years ago

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

库链接的顺序如果库彼此依赖则很重要 . 通常,如果库 A 依赖于库 B ,则出现 libA MUSTlibB 之前的链接器标志 .

例如:

// 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 问题!

3 years ago

what is an "undefined reference/unresolved external symbol"

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

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

好吧,我们破解它:)

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

3 years ago

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

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

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

C链接器期望名称被修改,因此您必须将该函数声明为:

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

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

extern "C" void foo();

并尝试在具有C链接的C程序中使用它 .

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

extern "C" {
    #include "cheader.h"
}

3 years ago

If all else fails, recompile.

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

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

3 years ago

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

3 years ago

模板实现不可见 .

非专业化模板必须使其定义对使用它们的所有翻译单元可见 . 这意味着您无法将模板的定义与实现文件分开 . 如果必须将实现分开,通常的解决方法是使用 impl 文件,该文件包含在声明模板的标头的末尾 . 常见的情况是:

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

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

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

要解决此问题,您必须将 X::foo 的定义移动到头文件或使用它的翻译单元可见的某个位置 .

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

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

3 years ago

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

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

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

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

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

  • 显式或隐式指定为此构建应用程序的其他依赖项的所有库(.lib) .

此搜索过程称为resolve .

D. Finally, why Unresolved External Symbol? 如果链接器无法在内部找到没有定义的符号的外部定义,则会报告未解析的外部符号错误 .

E. Possible causes of LNK2019 :未解决的外部符号错误 . 我们已经知道这个错误是由于链接器未能找到外部符号的定义,可能的原因可以排序为:

  • 定义存在

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

int foo()
{
    return 0;
}

在b.cpp中我们想调用函数foo,所以我们添加

void foo();

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

void bar()
{
    foo();
}

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

  • 定义不存在

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

3 years ago

undefined reference to WinMain@16 or similar 'unusual' main() entry point reference (特别是visual-studio) .

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

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


这里有case1case2从现实问题中更详细地处理 .

3 years ago

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

3 years ago

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

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

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

3 years ago

Visual Studio NuGet package needs to be updated for new toolset version

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

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

我通过查找 packagename\build\native\packagename.targets 编辑了包(在解决方案目录中的 packages 文件夹中)并在该文件中复制了所有 v110 部分 . 我在 the condition fields only 中将 v110 更改为 v120 非常小心,将文件名路径全部保留为 v110 . 这只是允许Visual Studio 2013链接到2012的库,在这种情况下,它工作 .

3 years ago

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

What's the meaning of compilation ?

  • Visual Studio搜索文件 .vcxproj 并开始编译扩展名为.cpp的每个文件 . 编译顺序是未定义的 . 所以你不能假设首先编译main.cpp文件

  • 如果.cpp文件依赖于其他.h文件,以便查找文件中可能定义或未定义的符号.cpp

  • 如果存在一个.cpp文件,其中编译器找不到一个符号,则 compiler time error 引发消息符号x无法找到

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

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

Steps In Linking a project

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

  • 如果在目标文件中找不到一个符号,他也会在附加库中搜索 . 为了向项目添加新库 Configuration properties - > VC++ Directories - > Library Directories ,这里你指定了用于搜索库的附加文件夹和 Configuration properties - > Linker - > Input 用于指定库的名称 . - 如果链接器找不到你在一个.cpp中写的符号,他会引发一个 linker time error 听起来像 error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observation

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

  • 链接库 does matter 的顺序 .

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

How To Solve this kind of error

编译器时间错误:

  • 确保编写c项目语法正确 .

链接器时间错误

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

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

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

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

3 years ago

编译器/ IDE中的错误

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

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

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

  • 尝试启动一个新项目,复制原始项目中的所有源代码 .

3 years ago

Linked .lib file is associated to a .dll

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

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

3 years ago

使用链接器帮助诊断错误

大多数现代链接器都包含一个不同程度打印的详细选项;

  • 链接调用(命令行),

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

  • 图书馆的位置,

  • 搜索路径用过的 .

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

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

3 years ago

由于人们似乎在链接器错误方面被引导到这个问题,我将在这里添加它 .

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

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

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

3 years ago

A wrapper around GNU ld that doesn't support linker scripts

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

INPUT (libtbb.so.2)

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

cp libtbb.so.2 libtbb.so

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

3 years ago

交友模板......

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

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)
    { /*...*/ }
    // ...
};

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

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

还有一些模板朋友在cppreferenceC++ FAQ阅读 .

Code listing showing the techniques above .


作为失败代码示例的旁注; g警告如下

警告:朋友声明'std :: ostream&operator <<(...)'声明一个非模板函数[-Wnon-template-friend]注意:(如果这不是你想要的,请确保函数模板有已经声明并在函数名后添加<>)

3 years ago

UNICODE定义不一致

Windows UNICODE构建使用 TCHAR 等构建,定义为 wchar_t 等 . 当不构建 UNICODE 定义为构建时 TCHAR 定义为 char 等 . 这些 UNICODE_UNICODE 定义影响所有"T" string types; LPTSTRLPCTSTR 和他们的麋鹿 .

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

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

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

  • 这可以用任何一种方法完成;
#define UNICODE
#define _UNICODE
  • 或在项目设置中;

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

  • 或在命令行上;
/DUNICODE /D_UNICODE

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

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

3 years ago

清理并重建

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

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

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

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

3 years ago

当包含路径不同时

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

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

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

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

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

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

你可能会问这样的情况会怎样结束? Include paths 当然!如果在编译共享库时,包含路径会导致 header1.h ,并且您最终在自己的程序中使用了 header2.h ,那么您将会 grab 您的 Headers ,想知道发生了什么(双关语意图) .

下面解释了现实世界中如何发生这种情况的一个例子 .

进一步阐述一个例子

我有两个项目: 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是您的朋友 . 我相信其他编译器还有其他类似的工具 .

过程如下:

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

  • 转储导出的从库中的符号到文本文件 .

  • 搜索感兴趣的导出符号,并注意到受损名称不同 .

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

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

[1]项目是指一组源文件,它们链接在一起以生成库或可执行文件 .

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

3 years ago

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

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

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

  • 如果 libfoo 取决于 libbar ,那么您的链接正确 libfoolibbar 之前 .

  • 您的链接因 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在您的发行版上生成的链接命令会在命令行序列中从左到右使用链接中的文件 . 当它发现某个文件引用某个内容并且不包含该内容的定义时,将在右侧的文件中搜索定义 . 如果它最终找到定义,则引用将被解析 . 如果任何引用在结束时仍未解析,则链接将失败:链接器不会向后搜索 .

首先, example 1 ,带静态库 my_lib.a

静态库是目标文件的索引存档 . 当链接器在链接序列中找到 -lmy_lib 并发现它引用静态库 ./libmy_lib.a 时,它想知道您的程序是否需要 libmy_lib.a 中的任何目标文件 .

libmy_lib.a 中只有目标文件,即 my_lib.omy_lib.o 中只定义了一个东西,即函数 hw .

链接器将决定您的程序是否需要 my_lib.o 当且仅当它已经知道您的程序引用了 hw 时,它已经添加到程序中的一个或多个目标文件中,并且它已经添加了所有目标文件包含 hw 的定义 .

如果是这样,那么链接器将从库中提取 my_lib.o 的副本并将其添加到您的程序中 . 然后,您的程序包含 hw 的定义,因此解析了对 hw 的引用 .

当您尝试链接程序时:

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

链接器在看到 -lmy_lib 时未向程序添加 eg1.o . 因为在那一点上,它还没有见过 eg1.o . 您的程序尚未对 hw 进行任何引用:它根本没有引用任何引用,因为它所做的所有引用都在 eg1.o 中 .

因此链接器不会将 my_lib.o 添加到程序中,并且不再用于 libmy_lib.a .

接下来,它找到 eg1.o ,并将其添加到程序中 . 中的一个目标文件链接序列始终添加到程序中 . 现在,程序引用 hw ,并且不包含 hw 的定义;但是链接序列中没有任何东西可以提供缺失的定义 . 对 hw 的引用最终未解决,并且链接失败 .

二, example 2 ,共享库 libz

共享库不是一个没有 main 函数的程序,而是公开它定义的多个其他符号,以便其他程序可以在运行时使用它们 .

今天许多Linux发行版都配置了他们的GCC工具链,以便其语言驱动程序( gccg++gfortran 等)指示系统链接器( ld )根据需要链接共享库 . 你有一个发行版 .

这意味着当链接器在链接序列中找到 -lz ,并且发现它引用共享库(比如) /usr/lib/x86_64-linux-gnu/libz.so 时,它想知道它添加到程序中的任何尚未定义的引用是否具有定义由 libz 导出

如果这是真的,那么链接器将不会复制 libz 中的任何块并将它们添加到您的程序中;相反,它只会医生你的程序代码,以便: -

  • 在运行时,系统程序加载程序会在加载程序副本时将 libz 的副本加载到与程序相同的进程中,以运行它 .

  • 在运行时,只要程序引用 libz 中定义的内容,该引用就会在同一进程中使用 libz 副本导出的定义 .

你的程序只想引用一个由 libz 导出定义的东西,即函数 zlibVersion ,在 eg2.c 中只引用一次 . 如果链接器将该引用添加到您的程序,然后找到 libz 导出的定义,则引用将被解析

但是当你尝试链接程序时:

gcc -o eg2 -lz eg2.o

事件的顺序与示例1的方式相同 . 在链接器找到 -lz 时,程序中没有任何引用:它们都在 eg2.o 中,但尚未见到 . 因此链接器决定它对 libz 没用 . 当它到达 eg2.o 时,将其添加到程序中,然后对 zlibVersion 进行未定义的引用,链接序列完成;该引用未解析,并且链接失败 .

最后,示例2的 pkg-config 变体现在有一个明显的解释 . 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中解释的需要链接,但共享库无条件链接 .

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

  • 在运行时这是不经济的,因为它可以导致共享库与程序一起加载,即使它们不需要它们也是如此 .

  • 静态和共享库的不同链接规则可能会让不熟练的程序员感到困惑,他们可能不知道他们的链接中的 -lfoo 是否将解析为 /some/where/libfoo.a/some/where/libfoo.so ,并且可能无法理解共享库和静态库之间的区别 .

这种权衡导致了今天的分裂局面 . 一些发行版已经改变了共享库的GCC链接规则,因此根据需要的原则适用于所有库 . 一些发行版坚持旧的方式 .

即使我同时编译和链接,为什么我仍然会遇到这个问题?

如果我这样做:

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

当然gcc必须先编译 eg1.c ,然后将生成的目标文件与 libmy_lib.a 链接起来 . 那么在它做的时候怎么也不知道对象文件是否需要链接?

因为使用单个命令进行编译和链接不会改变链接序列的顺序 .

当您运行上面的命令时, gcc 指出您想要编译链接 . 所以在幕后,它会生成一个编译命令,然后运行它,然后生成一个连接命令,并运行它,就像你运行了两个命令一样:

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

因此,如果您运行这两个命令,链接就会失败 . 你在失败中注意到的唯一区别是gcc在编译链接的情况下生成了一个临时目标文件,因为你没有告诉它使用 eg1.o . 我们看:

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

代替:

eg1.o: In function `main':

另见

The order in which interdependent linked libraries are specified is wrong

将相互依赖的库置于错误的顺序只是一种方法,在这种方法中,您可以获得需要在链接中稍后定义的文件的文件,而不是提供定义的文件 . 将库放在引用它们的目标文件之前是另一种造成同样错误的方法 .

3 years ago

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

对于来自C的人来说,在C global 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 变量

3 years ago

尽管这是一个非常古老的问题,有多个已接受的答案,但我想分享如何解决 obscure "undefined reference to"错误 .

不同版本的库

我使用别名来引用 std::filesystem::path :文件系统是从C 17开始的标准库,但我的程序需要 also compile in 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's < experimental::filestystem >并包含上述代码

  • file.cpp ,file.h的实现,#include的“ file.h

  • main.cpp #include's < filestystem >和“ file.h

注意main.cpp和file.h中使用的 different libraries . 由于在< filestystem >之后main.cpp #include'd“ file.h ”,因此使用的文件系统版本为 the C++17 one . 我曾经使用以下命令编译程序:

$ 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

这样 any function 包含在file.o中并在main.o中使用 required path_t 给出了"undefined reference"错误,因为 main.o 指的是 std::filesystem::path 但是 file.o 指的是 std::experimental::filesystem::path .

分辨率

为了解决这个问题,我只需要 change experimental::filestystem in file.h to <filestystem> .

3 years ago

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

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

您可以通过调用来检查您要查找的符号是否为外部符号:

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

隐藏/本地符号由 nm 显示,带有小写符号类型,例如 t 而不是`T代码段:

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

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

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

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

其中大致对应Windows'/ MSVC版本:

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

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


当使用 -fvisibility=hidden 编译转换单元时,结果符号仍然具有外部链接(通过 nm 以大写符号类型显示),并且如果目标文件成为静态库的一部分,则可以用于外部链接而不会出现问题 . 仅当目标文件链接到共享库时,链接才会变为本地链接 .

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

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

3 years ago

Different architectures

您可能会看到如下消息:

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

在这种情况下,这意味着可用符号的结构与您编译的不同 .

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

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

在MacOS上,可以选择在同一文件中传送两种体系结构 . 链接可能需要两个版本,但只有一个版本 . 它也可能是错误的 lib / lib64 文件夹中存在库的问题 .