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


当前回答

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

不同版本的库

我使用别名来引用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>。

其他回答

模板实现不可见。

非专用模板的定义必须对使用它们的所有翻译单位可见。这意味着不能分离模板的定义到实现文件。如果必须分离实现,通常的解决方法是在头的末尾包含一个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的定义移动到头文件或使用它的翻译单元可见的某个位置。

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

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

我的例子:

头文件

class GameCharacter : public GamePart
{
    private:
        static vector<GameCharacter*> characterList;
...
}

.cpp文件:

vector<GameCharacter*> characterList;

这产生了“未定义”加载程序错误,因为“characterList”被声明为静态成员变量,但被定义为全局变量。

我加上这个是因为——虽然其他人在一长串需要注意的事情中列出了这个案例——但这个列表并没有给出示例。这是一个更值得寻找的例子,尤其是在C++中。

修复方法是向全局变量添加限定以定义静态数据成员:

vector<GameCharacter*> GameCharacter::characterList;

同时保持收割台相同。

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

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

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

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

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

MSVS要求您使用__declspec(dllexport)和__declsspec(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
{
};