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

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

随意编辑/添加自己的。

编译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. 解析所有外部实体引用。 链接库组件以满足对当前转换中未定义的实体的外部引用。 所有这样的翻译器输出被收集到程序映像中,该程序映像包含在其执行环境中执行所需的信息。 (强调我的)

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

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

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

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

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

常见错误消息是error LNK2001error LNK1120Microsoft Visual Studio error LNK2019GCC的 symbolName undefined reference to

代码:

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

将在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 ++代码中使用。
  • 跨modules / dll错误地导入/导出方法/类。 (MSVS具体)
  • 循环库依赖
  • 未定义的引用`WinMain @ 16′
  • 相互依赖的图书馆订单
  • 多个同名的源文件
  • 使用#pragma (Microsoft Visual Studio)时是否包含.lib扩展名?
  • 模板朋友的问题
  • UNICODE定义不一致

class级成员:

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方法类似,添加的理由是纯声明生成虚拟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数据成员进行初始化。

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

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

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

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

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

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

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

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

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

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

典型的变量声明是

 extern int x; 

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

 int x; 

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

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

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

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

请注意,您实现的function与您声明的function完全匹配。 例如,您可能有不匹配的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) 

其他不匹配的例子包括

  • 在一个命名空间中声明的函数/变量,在另一个命
  • 声明为类成员的函数/变量,定义为全局(反之亦然)。
  • 函数返回类型,参数号和类型以及调用约定都不完全一致。

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

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

库链接的顺序如果库彼此依赖则很重要。 通常,如果库A依赖于库B ,那么libA 必须出现在链接器标志中的libA之前。

例如:

 // Bh #ifndef B_H #define B_H struct B { B(int); int x; }; #endif // B.cpp #include "Bh" B::B(int xx) : x(xx) {} // Ah #include "Bh" struct A { A(int x); B b; }; // A.cpp #include "Ah" A::A(int x) : b(x) {} // main.cpp #include "Ah" int main() { A a(5); return 0; }; 

创建库:

 $ g++ -c A.cpp $ g++ -c B.cpp $ ar rvs libA.a Ao ar: creating libA.a a - Ao $ ar rvs libB.a Bo ar: creating libB.a a - Bo 

编译:

 $ g++ main.cpp -L. -lB -lA ./libA.a(Ao): 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 

所以重复一次,订单很重要!

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

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

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

好的,我们破解它:)

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

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

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

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

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

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

MSVS要求您使用__declspec(dllexport)__declspec(dllimport)指定要导出和导入的符号。

这种双重function通常通过使用宏来获得:

 #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 { }; 

模板实现不可见。

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

 template struct X { void foo(); }; int main() { X x; x.foo(); } //differentImplementationFile.cpp template void X::foo() { } 

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

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

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

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

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

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

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

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

此搜索过程称为resolve。

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设置的附加依赖项列表(设置自: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency )中。 现在链接器将报告LNK2019,因为当前搜索范围中不存在定义。

未定义引用WinMain@16或类似的‘不常’ main()入口点引用 (特别是对于visual-studio )。

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

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


这里有case1和case2从现实世界问题中更详细地处理。

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

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

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

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

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

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

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

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

假设你有一个用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完成的。链接器应合并所有目标文件并最终构建输出(可能是可执行文件或库)

链接项目的步骤

  • 解析所有目标文件并找到仅在头文件中声明的定义(例如:在前面的答案中提到的类的一个方法的代码,或者事件是在类中成员的静态变量的初始化)
  • If one symbol could not be found in object files he also is searched in Additional Libraries.For adding a new library to a project Configuration properties -> VC++ Directories -> Library Directories and here you specified additional folder for searching libraries and Configuration properties -> Linker -> Input for specifying the name of the library. -If the Linker could not find the symbol which you write in one .cpp he raises a linker time error which may sound like error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

意见

  1. Once the Linker find one symbol he doesn’t search in other libraries for it
  2. The order of linking libraries does matter .
  3. If Linker finds an external symbol in one static library he includes the symbol in the output of the project.However, if the library is shared( dynamic ) he doesn’t include the code ( symbols ) in output, but Run-Time crashes may occur

How To Solve this kind of error

Compiler Time Error :

  • Make sure you write your c++ project syntactical correct.

Linker Time Error

  • Define all your symbol which you declare in your header files
  • Use #pragma once for allowing compiler not to include one header if it was already included in the current .cpp which are compiled
  • Make sure that your external library doesn’t contain symbols that may enter into conflict with other symbols you defined in your header files
  • When you use the template to make sure you include the definition of each template function in the header file for allowing the compiler to generate appropriate code for any instantiations.

A bug in the compiler/IDE

I recently had this problem, and it turned out it was a bug in Visual Studio Express 2013 . I had to remove a source file from the project and re-add it to overcome the bug.

Steps to try if you believe it could be a bug in compiler/IDE:

  • Clean the project (some IDEs have an option to do this, you can also manually do it by deleting the object files)
  • Try start a new project, copying all source code from the original one.

Linked .lib file is associated to a .dll

我遇到过同样的问题。 Say i have projects MyProject and TestProject. I had effectively linked the lib file for MyProject to the TestProject. However, this lib file was produced as the DLL for the MyProject was built. Also, I did not contain source code for all methods in the MyProject, but only access to the DLL’s entry points.

To solve the issue, i built the MyProject as a LIB, and linked TestProject to this .lib file (i copy paste the generated .lib file into the TestProject folder). I can then build again MyProject as a DLL. It is compiling since the lib to which TestProject is linked does contain code for all methods in classes in MyProject.

Use the linker to help diagnose the error

Most modern linkers include a verbose option that prints out to varying degrees;

  • Link invocation (command line),
  • Data on what libraries are included in the link stage,
  • The location of the libraries,
  • Search paths used.

For gcc and clang; you would typically add -v -Wl,--verbose or -v -Wl,-v to the command line. More details can be found here;

  • Linux ld man page .
  • LLVM linker page .
  • “An introduction to GCC” chapter 9 .

For MSVC, /VERBOSE (in particular /VERBOSE:LIB ) is added to the link command line.

  • The MSDN page on the /VERBOSE linker option .

Since people seem to be directed to this question when it comes to linker errors I am going to add this here.

One possible reason for linker errors with GCC 5.2.0 is that a new libstdc++ library ABI is now chosen by default.

If you get linker errors about undefined references to symbols that involve types in the std::__cxx11 namespace or the tag [abi:cxx11] then it probably indicates that you are trying to link together object files that were compiled with different values for the _GLIBCXX_USE_CXX11_ABI macro. This commonly happens when linking to a third-party library that was compiled with an older version of GCC. If the third-party library cannot be rebuilt with the new ABI then you will need to recompile your code with the old ABI.

So if you suddenly get linker errors when switching to a GCC after 5.1.0 this would be a thing to check out.

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

Some .so files are actually GNU ld linker scripts , eg libtbb.so file is an ASCII text file with this contents:

 INPUT (libtbb.so.2) 

Some more complex builds may not support this. For example, if you include -v in the compiler options, you can see that the mainwin gcc wrapper mwdip discards linker script command files in the verbose output list of libraries to link in. A simple work around is to replace the linker script input command file with a copy of the file instead (or a symlink), eg

 cp libtbb.so.2 libtbb.so 

Or you could replace the -l argument with the full path of the .so, eg instead of -ltbb do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

Befriending templates…

Given the code snippet of a template type with a friend operator (or function);

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

The operator<< is being declared as a non-template function. For every type T used with Foo , there needs to be a non-templated operator<< . For example, if there is a type Foo declared, then there must be an operator implementation as follows;

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

Since it is not implemented, the linker fails to find it and results in the error.

To correct this, you can declare a template operator before the Foo type and then declare as a friend, the appropriate instantiation. The syntax is a little awkward, but is looks as follows;

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

The above code limits the friendship of the operator to the corresponding instantiation of Foo , ie the operator<< instantiation is limited to access the private members of the instantiation of Foo .

Alternatives include;

  • Allowing the friendship to extend to all instantiations of the templates, as follows;

     template  class Foo { template  friend std::ostream& operator<<(std::ostream& os, const Foo& a); // ... }; 
  • Or, the implementation for the operator<< can be done inline inside the class definition;

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

Note , when the declaration of the operator (or function) only appears in the class, the name is not available for "normal" lookup, only for argument dependent lookup, from cppreference ;

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not accessible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided...

There is further reading on template friends at cppreference and the C++ FAQ .

Code listing showing the techniques above .


As a side note to the failing code sample; g++ warns about this as follows

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)

Inconsistent UNICODE definitions

A Windows UNICODE build is built with TCHAR etc. being defined as wchar_t etc. When not building with UNICODE defined as build with TCHAR defined as char etc. These UNICODE and _UNICODE defines affect all the ” T ” string types ; LPTSTR , LPCTSTR and their elk.

Building one library with UNICODE defined and attempting to link it in a project where UNICODE is not defined will result in linker errors since there will be a mismatch in the definition of TCHAR ; char vs. wchar_t .

The error usually includes a function a value with a char or wchar_t derived type, these could include std::basic_string<> etc. as well. When browsing through the affected function in the code, there will often be a reference to TCHAR or std::basic_string etc. This is a tell-tale sign that the code was originally intended for both a UNICODE and a Multi-Byte Character (or “narrow”) build.

To correct this, build all the required libraries and projects with a consistent definition of UNICODE (and _UNICODE ).

  1. This can be done with either;

     #define UNICODE #define _UNICODE 
  2. Or in the project settings;

    Project Properties > General > Project Defaults > Character Set

  3. Or on the command line;

     /DUNICODE /D_UNICODE 

The alternative is applicable as well, if UNICODE is not intended to be used, make sure the defines are not set, and/or the multi-character setting is used in the projects and consistently applied.

Do not forget to be consistent between the “Release” and “Debug” builds as well.

Clean and rebuild

A “clean” of the build can remove the “dead wood” that may be left lying around from previous builds, failed builds, incomplete builds and other build system related build issues.

In general the IDE or build will include some form of “clean” function, but this may not be correctly configured (eg in a manual makefile) or may fail (eg the intermediate or resultant binaries are read-only).

Once the “clean” has completed, verify that the “clean” has succeeded and all the generated intermediate file (eg an automated makefile) have been successfully removed.

This process can be seen as a final resort, but is often a good first step ; especially if the code related to the error has recently been added (either locally or from the source repository).

Your linkage consumes libraries before the object files that refer to them

  • You are trying to compile and link your program with the GCC toolchain.
  • Your linkage specifies all of the necessary libraries and library search paths
  • If libfoo depends on libbar , then your linkage correctly puts libfoo before libbar .
  • Your linkage fails with undefined reference to something errors.
  • But all the undefined something s are declared in the header files you have #include d and are in fact defined in the libraries that you are linking.

Examples are in C. They could equally well be C++

A minimal example involving a static library you built yourself

my_lib.c

 #include "my_lib.h" #include  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  int main() { hw(); return 0; } 

You build your static library:

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

You compile your program:

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

You try to link it with libmy_lib.a and fail:

 $ 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 

The same result if you compile and link in one step, like:

 $ 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 

A minimal example involving a shared system library, the compression library libz

eg2.c

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

Compile your program:

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

Try to link your program with libz and fail:

 $ 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 

Same if you compile and link in one go:

 $ 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 

And a variation on example 2 involving pkg-config :

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

你做错了什么?

In the sequence of object files and libraries you want to link to make your program, you are placing the libraries before the object files that refer to them. You need to place the libraries after the object files that refer to them.

Link example 1 correctly:

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

Success:

 $ ./eg1 Hello World 

Link example 2 correctly:

 $ gcc -o eg2 eg2.o -lz 

Success:

 $ ./eg2 1.2.8 

Link the example 2 pkg-config variation correctly:

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

说明

Reading is optional from here on .

By default, a linkage command generated by GCC, on your distro, consumes the files in the linkage from left to right in commandline sequence. When it finds that a file refers to something and does not contain a definition for it, to will search for a definition in files further to the right. If it eventually finds a definition, the reference is resolved. If any references remain unresolved at the end, the linkage fails: the linker does not search backwards.

First, example 1 , with static library my_lib.a

A static library is an indexed archive of object files. When the linker finds -lmy_lib in the linkage sequence and figures out that this refers to the static library ./libmy_lib.a , it wants to know whether your program needs any of the object files in libmy_lib.a .

There is only object file in libmy_lib.a , namely my_lib.o , and there’s only one thing defined in my_lib.o , namely the function hw .

The linker will decide that your program needs my_lib.o if and only if it already knows that your program refers to hw , in one or more of the object files it has already added to the program, and that none of the object files it has already added contains a definition for hw .

If that is true, then the linker will extract a copy of my_lib.o from the library and add it to your program. Then, your program contains a definition for hw , so its references to hw are resolved .

When you try to link the program like:

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

the linker has not added eg1.o to the program when it sees -lmy_lib . Because at that point, it has not seen eg1.o . Your program does not yet make any references to hw : it does not yet make any references at all , because all the references it makes are in eg1.o .

So the linker does not add my_lib.o to the program and has no further use for libmy_lib.a .

Next, it finds eg1.o , and adds it to be program. An object file in the linkage sequence is always added to the program. Now, the program makes a reference to hw , and does not contain a definition of hw ; but there is nothing left in the linkage sequence that could provide the missing definition. The reference to hw ends up unresolved , and the linkage fails.

Second, example 2 , with shared library libz

A shared library isn’t an archive of object files or anything like it. It’s much more like a program that doesn’t have a main function and instead exposes multiple other symbols that it defines, so that other programs can use them at runtime.

Many Linux distros today configure their GCC toolchain so that its language drivers ( gcc , g++ , gfortran etc) instruct the system linker ( ld ) to link shared libraries on an as-needed basis. You have got one of those distros.

This means that when the linker finds -lz in the linkage sequence, and figures out that this refers to the shared library (say) /usr/lib/x86_64-linux-gnu/libz.so , it wants to know whether any references that it has added to your program that aren’t yet defined have definitions that are exported by libz

If that is true, then the linker will not copy any chunks out of libz and add them to your program; instead, it will just doctor the code of your program so that:-

  • At runtime, the system program loader will load a copy of libz into the same process as your program whenever it loads a copy of your program, to run it.

  • At runtime, whenever your program refers to something that is defined in libz , that reference uses the definition exported by the copy of libz in the same process.

Your program wants to refer to just one thing that has a definition exported by libz , namely the function zlibVersion , which is referred to just once, in eg2.c . If the linker adds that reference to your program, and then finds the definition exported by libz , the reference is resolved

But when you try to link the program like:

 gcc -o eg2 -lz eg2.o 

the order of events is wrong in just the same way as with example 1. At the point when the linker finds -lz , there are no references to anything in the program: they are all in eg2.o , which has not yet been seen. So the linker decides it has no use for libz . When it reaches eg2.o , adds it to the program, and then has undefined reference to zlibVersion , the linkage sequence is finished; that reference is unresolved, and the linkage fails.

Lastly, the pkg-config variation of example 2 has a now obvious explanation. After shell-expansion:

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

变为:

 gcc -o eg2 -lz eg2.o 

which is just example 2 again.

I can reproduce the problem in example 1, but not in example 2

The linkage:

 gcc -o eg2 -lz eg2.o 

works just fine for you!

(Or: That linkage worked fine for you on, say, Fedora 23, but fails on Ubuntu 16.04)

That’s because the distro on which the linkage works is one of the ones that does not configure its GCC toolchain to link shared libraries as-needed .

Back in the day, it was normal for unix-like systems to link static and shared libraries by different rules. Static libraries in a linkage sequence were linked on the as-needed basis explained in example 1, but shared libraries were linked unconditionally.

This behaviour is economical at linktime because the linker doesn’t have to ponder whether a shared library is needed by the program: if it’s a shared library, link it. And most libraries in most linkages are shared libraries. But there are disadvantages too:-

  • It is uneconomical at runtime , because it can cause shared libraries to be loaded along with a program even if doesn’t need them.

  • The different linkage rules for static and shared libraries can be confusing to inexpert programmers, who may not know whether -lfoo in their linkage is going to resolve to /some/where/libfoo.a or to /some/where/libfoo.so , and might not understand the difference between shared and static libraries anyway.

This trade-off has led to the schismatic situation today. Some distros have changed their GCC linkage rules for shared libraries so that the as-needed principle applies for all libraries. Some distros have stuck with the old way.

Why do I still get this problem even if I compile-and-link at the same time?

If I just do:

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

surely gcc has to compile eg1.c first, and then link the resulting object file with libmy_lib.a . So how can it not know that object file is needed when it’s doing the linking?

Because compiling and linking with a single command does not change the order of the linkage sequence.

When you run the command above, gcc figures out that you want compilation + linkage. So behind the scenes, it generates a compilation command, and runs it, then generates a linkage command, and runs it, as if you had run the two commands:

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

So the linkage fails just as it does if you do run those two commands. The only difference you notice in the failure is that gcc has generated a temporary object file in the compile + link case, because you’re not telling it to use eg1.o . We see:

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

代替:

 eg1.o: In function `main': 

也可以看看

The order in which interdependent linked libraries are specified is wrong

Putting interdependent libraries in the wrong order is just one way in which you can get files that need definitions of things coming later in the linkage than the files that provide the definitions. Putting libraries before the object files that refer to them is another way of making the same mistake.

When your include paths are different

Linker errors can happen when a header file and its associated shared library (.lib file) go out of sync. 让我解释。

How do linkers work? The linker matches a function declaration (declared in the header) with its definition (in the shared library) by comparing their signatures. You can get a linker error if the linker doesn’t find a function definition that matches perfectly.

Is it possible to still get a linker error even though the declaration and the definition seem to match? 是! They might look the same in source code, but it really depends on what the compiler sees. Essentially you could end up with a situation like this:

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

Note how even though both the function declarations look identical in source code, but they are really different according to the compiler.

You might ask how one ends up in a situation like that? Include paths of course! If when compiling the shared library, the include path leads to header1.h and you end up using header2.h in your own program, you’ll be left scratching your header wondering what happened (pun intended).

An example of how this can happen in the real world is explained below.

Further elaboration with an example

I have two projects: graphics.lib and main.exe . Both projects depend on common_math.h . Suppose the library exports the following function:

 // graphics.lib #include "common_math.h" void draw(vec3 p) { ... } // vec3 comes from common_math.h 

And then you go ahead and include the library in your own project.

 // main.exe #include "other/common_math.h" #include "graphics.h" int main() { draw(...); } 

繁荣! You get a linker error and you have no idea why it’s failing. The reason is that the common library uses different versions of the same include common_math.h (I have made it obvious here in the example by including a different path, but it might not always be so obvious. Maybe the include path is different in the compiler settings).

Note in this example, the linker would tell you it couldn’t find draw() , when in reality you know it obviously is being exported by the library. You could spend hours scratching your head wondering what went wrong. The thing is, the linker sees a different signature because the parameter types are slightly different. In the example, vec3 is a different type in both projects as far as the compiler is concerned. This could happen because they come from two slightly different include files (maybe the include files come from two different versions of the library).

Debugging the linker

DUMPBIN is your friend, if you are using Visual Studio. I’m sure other compilers have other similar tools.

The process goes like this:

  1. Note the weird mangled name given in the linker error. (eg. draw@graphics@XYZ).
  2. Dump the exported symbols from the library into a text file.
  3. Search for the exported symbol of interest, and notice that the mangled name is different.
  4. Pay attention to why the mangled names ended up different. You would be able to see that the parameter types are different, even though they look the same in the source code.
  5. Reason why they are different. In the example given above, they are different because of different include files.

[1] By project I mean a set of source files that are linked together to produce either a library or an executable.

EDIT 1: Rewrote first section to be easier to understand. Please comment below to let me know if something else needs to be fixed. 谢谢!

Missing “extern” in const variable declarations/definitions (C++ only)

For people coming from C it might be a surprise that in C++ global const variables have internal (or static) linkage. In C this was not the case, as all global variables are implicitly extern (ie when the static keyword is missing).

例:

 // 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 } 

correct would be to use a header file and include it in file2.cpp and file1.cpp

 extern const int test; extern int test2; 

Alternatively one could declare the const variable in file1.cpp with explicit extern

Even though this is a pretty old questions with multiple accepted answers, I’d like to share how to resolve an obscure “undefined reference to” error.

Different versions of libraries

I was using an alias to refer to std::filesystem::path : filesystem is in the standard library since C++17 but my program needed to also compile in C++14 so I decided to use a variable alias:

 #if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: ) using path_t = std::experimental::filesystem::path; #elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: ) using path_t = std::filesystem::path; #endif 

Let’s say I have three files: main.cpp, file.h, file.cpp:

  • file.h #include’s < experimental::filestystem > and contains the code above
  • file.cpp , the implementation of file.h, #include’s ” file.h
  • main.cpp #include’s < filestystem > and ” file.h

Note the different libraries used in main.cpp and file.h. Since main.cpp #include’d ” file.h ” after < filestystem >, the version of filesystem used there was the C++17 one . I used to compile the program with the following commands:

$ g++ -g -std=c++17 -c main.cpp -> compiles main.cpp to main.o
$ g++ -g -std=c++17 -c file.cpp -> compiles file.cpp and file.h to file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs -> links main.o and file.o

This way any function contained in file.o and used in main.o that required path_t gave “undefined reference” errors because main.o referred to std::filesystem::path but file.o to std::experimental::filesystem::path .

解析度

To fix this I just needed to change in file.h to .

When linking against shared libraries, make sure that the used symbols are not hidden.

The default behavior of gcc is that all symbols are visible. However, when the translation units are built with option -fvisibility=hidden , only functions/symbols marked with __attribute__ ((visibility ("default"))) are external in the resulting shared object.

You can check whether the symbols your are looking for are external by invoking:

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

the hidden/local symbols are shown by nm with lowercase symbol type, for example t instead of `T for code-section:

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

You can also use nm with the option -C to demangle the names (if C++ was used).

Similar to Windows-dlls, one would mark public functions with a define, for example DLL_PUBLIC defined as:

 #define DLL_PUBLIC __attribute__ ((visibility ("default"))) DLL_PUBLIC int my_public_function(){ ... } 

Which roughly corresponds to Windows’/MSVC-version:

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

More information about visibility can be found on the gcc wiki.


When a translation unit is compiled with -fvisibility=hidden the resulting symbols have still external linkage (shown with upper case symbol type by nm ) and can be used for external linkage without problem if the object files become part of a static libraries. The linkage becomes local only when the object files are linked into a shared library.

To find which symbols in an object file are hidden run:

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