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


当前回答

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

最近,我只需重新编译有问题的文件,就可以消除Visual Studio 2012中未解决的外部错误。当我重新构建时,错误消失了。

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

其他回答

使用链接器帮助诊断错误

大多数现代链接器都有一个冗长的选项,可以在不同程度上打印出来;

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

用于gcc和clang;通常会在命令行中添加-v-Wl、--verbose或-v-Wl和-v。更多详情请点击此处;

Linux ld手册页。LLVM链接器页。“GCC简介”第9章。

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

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

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

不同版本的库

我使用别名来引用std::filesystem::path:filesystem自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的<实验::filesystem>,并包含上面的代码file.cpp,file.h的实现,#include的“file.h”main.cpp#include的<文件系统>和“file.h”

注意main.cpp和file.h中使用的不同库。由于main.cpp#在<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可执行文件main.o file.o-lsdc++fs->链接main.o和file.o

这样,任何包含在file.o中并在main.o中使用的需要path_t的函数都会出现“未定义的引用”错误,因为main.o引用std::filesystem::path,而file.o引用的是std::experimental::filesystem::path。

决议

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

UNICODE定义不一致

Windows UNICODE构建时,TCHAR等被定义为wchar_t等。如果不使用UNICODE进行构建,则TCHAR被定义为char等。这些UNICODE和_UNICODE定义会影响所有“t”字符串类型;LPTSTR、LPCTSTR及其麋鹿。

构建一个定义了UNICODE的库,并试图将其链接到未定义UNICODE项目中,将导致链接器错误,因为TCHAR的定义将不匹配;char与wchar_t。

错误通常包括一个带有char或wchar_t派生类型的值的函数,这些类型也可能包括std::basic_string<>等。浏览代码中受影响的函数时,通常会引用TCHAR或std::basic_string<TCHAR>等。这是一个信号,表明代码最初用于UNICODE和多字节字符(或“窄”)构建。

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

这可以通过以下两种方式实现:;#定义UNICODE#定义UNICODE或在项目设置中;项目财产>常规>项目默认值>字符集或在命令行上;/DUNICODE/D-UNICODE

如果不打算使用UNICODE,请确保未设置定义,和/或在项目中使用了多字符设置,并始终应用。

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

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

对于来自C语言的人来说,在C++中,全局常量变量具有内部(或静态)联系可能是一件令人惊讶的事情。在C中情况并非如此,因为所有全局变量都是隐式外部变量(即,当缺少静态关键字时)。

例子:

// 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变量

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

编译的意义是什么?

Visual Studio搜索文件.vcxproj并开始编译扩展名为.cpp的每个文件。编译顺序未定义。因此,您不能假设首先编译了main.cpp文件如果.cpp文件依赖于其他.h文件来查找符号可以在.cpp文件中定义,也可以不定义如果存在一个.cpp文件,编译器在其中找不到一个符号,则编译器时间错误将引发消息symbol x not be found对于每个扩展名为.cpp的文件,都会生成一个对象文件.o,并且Visual Studio会将输出写入名为ProjectName.cpp.Clean.txt的文件中,该文件包含链接器必须处理的所有对象文件。

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

链接项目的步骤

分析所有对象文件,找到仅在头文件中声明的定义(例如:前面的答案中提到的类的一个方法的代码,或者初始化类内部的静态变量的事件)如果在目标文件中找不到一个符号,也会在其他库中搜索。对于将新库添加到项目的配置财产->VC++目录->库目录,您在此处指定了用于搜索库的其他文件夹,以及用于指定库名称的配置财产->链接器->输入。-如果链接器找不到您在一个.cpp中编写的符号,则会引发一个链接器时间错误,听起来可能像错误LNK2001:未解析的外部符号“void __cdecl foo(void)”(?foo@@YAXXZ)

观察

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

如何解决这种错误

编译器时间错误:

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

链接器时间错误

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