链接器工具错误 LNK2001

无法解析的外部符号“symbol”

编译后的代码引用或调用符号。 该符号未在链接器搜索的任何库或对象文件中定义。

此错误消息后为错误 LNK1120。 若要修复错误 LNK1120,请首先修复所有 LNK2001 和 LNK2019 错误。

可通过多种方法获取 LNK2001 错误。 所有这些方法都涉及对链接器无法解析或查找定义的函数或变量引用。 编译器可以识别代码何时未声明符号,但当它不定义符号时,编译器无法识别。 这是因为定义可能位于不同的源文件或库中。 如果代码引用了某个符号,但从未定义该符号,则链接器会生成错误。

什么是未解析的外部符号?

“符号”是函数或全局变量的内部名称。 它是在已编译的对象文件或库中使用或定义的名称形式。 全局变量在为其分配了存储的对象文件中进行定义。 函数在放置函数体的已编译代码的对象文件中进行定义。 “外部符号”是在一个对象文件中引用,但在不同的库或对象文件中定义的符号。 “导出的符号”是由定义它的对象文件或库公开提供的符号

若要创建应用程序或 DLL,使用的每个符号都必须有一个定义。 链接器必须解析或查找每个对象文件引用的每个外部符号的匹配定义。 链接器在无法解析外部符号时生成错误。 这意味着链接器在任何链接文件中都找不到匹配的导出符号的定义。

以下情况下可能发生此错误:

  • 当项目缺少对库 (.LIB) 或对象 (.OBJ) 文件的引用时。 若要解决此问题,请将对所需库或对象文件的引用添加到项目中。 有关详细信息,请参阅用作链接器输入的 lib 文件

  • 当项目具有对库 (.LIB) 或对象 (.OBJ) 文件的引用,而这些引用又需要来自另一个库的符号时。 即使不调用导致依赖关系的函数,也可能发生这种情况。 若要解决此问题,请将对其他库的引用添加到项目中。 有关详细信息,请参阅了解经典的链接模型:带上符号

  • 如果使用 /NODEFAULTLIB/Zl 选项。 指定这些选项时,包含所需代码的库不会链接到项目中,除非显式包含它们。 若要解决此问题,请显式包括在链接命令行上使用的所有库。 如果在使用这些选项时看到许多缺少的 CRT 或标准库函数名称,请在链接中显式包含 CRT 和标准库 DLL 或库文件。

  • 如果使用 /clr 选项进行编译。 可能缺少对 .cctor 的引用。 有关如何解决此问题的详细信息,请参阅混合程序集的初始化

  • 如果在生成应用程序的调试版本时链接到发布模式库。 同样,如果使用选项 /MTd 或 /MDd 或定义 _DEBUG,然后链接到发布库,则应该会遇到许多潜在的未解决的外部问题以及其他问题。 将发布模式版本与调试库链接也会导致类似的问题。 若要解决此问题,请确保在调试版本中使用调试库,在零售版本中使用零售库。

  • 如果代码引用了一个库版本中的符号,但链接了该库的不同版本。 通常,不能混合为不同版本的编译器生成的对象文件或库。 一个版本附带的库可能包含在其他版本附带的库中找不到的符号。 若要解决此问题,请先使用相同版本的编译器生成所有对象文件和库,然后再将它们链接在一起。 有关详细信息,请参阅 Visual Studio 版本之间的 C++ 二进制兼容性

  • 如果库路径已过期。 在“工具”>“选项”>“项目”>“VC++ 目录”对话框的“库文件”选择下,可以更改库搜索顺序。 项目的“属性页”对话框中的“链接器”文件夹还可能包含可能已过期的路径。

  • 当安装了新的 Windows SDK 时(可能安装到其他位置)。 必须更新库搜索顺序才能指向新位置。 通常,应将新 SDK 包含目录和 lib 目录的路径放在默认 Visual C++ 位置的前面。 此外,包含嵌入路径的项目可能仍指向有效但已过期的旧路径。 更新由安装到其他位置的新版本添加的新功能的路径。

  • 如果在命令行中生成,并且已经创建了自己的环境变量。 验证工具、库和头文件的路径是否指向一致的版本。 有关详细信息,请参阅通过命令行使用 MSVC 工具集

编码问题

此错误可由以下原因引起:

  • 源代码或模块定义 (.def) 文件中的大小写不匹配。 例如,如果在一个 C++ 源文件中将变量命名为 var1,并尝试在另一个源文件中作为 VAR1 访问它,则会生成此错误。 若要解决此问题,请使用拼写和大小写一致的名称。

  • 使用函数内联的项目。 将函数定义为源文件中的 inline 而不是头文件中的函数时,可能会发生这种情况。 内联函数在定义它们的源文件之外看不到。 若要解决此问题,请在声明内联函数的标头中定义内联函数。

  • 从 C++ 程序调用 C 函数,而不对 C 函数使用 extern "C" 声明。 编译器对 C 和 C++ 代码使用不同的内部符号命名约定。 内部符号名称是链接器在解析符号时查找的名称。 若要解决此问题,请对 C++ 代码中使用的 C 函数的所有声明使用 extern "C" 包装器,这会导致编译器对这些符号使用 C 内部命名约定。 编译器选项 /Tp/Tc 会导致编译器将文件分别编译为 C++ 或 C,而不管文件扩展名是什么。 这些选项可能会导致内部函数名称与预期名称不同。

  • 尝试引用没有外部链接的函数或数据。 在 C++ 中,除非显式指定为 extern,否则内联函数和 const 数据具有内部链接。 若要解决此问题,请对定义源文件外部引用的符号使用显式 extern 声明。

  • 缺少函数体或变量定义。 声明但不定义代码中的变量、函数或类时,此错误很常见。 编译器只需要一个函数原型或 extern 变量声明来生成一个没有错误的对象文件,但链接器无法解析对函数的调用或对变量的引用,因为没有保留函数代码或变量空间。 若要解决此问题,请确保在链接的源文件或库中定义每个引用的函数和变量。

  • 使用返回和参数类型或调用与函数定义中的约定不匹配的约定的函数调用。 在 C++ 对象文件中,名称修饰对函数的调用约定、类或命名空间范围以及返回类型和参数类型进行编码。 编码的字符串将成为最终修饰函数名称的一部分。 链接器使用此名称来解析或匹配来自其他对象文件的函数并对其进行调用。 若要解决此问题,请确保函数声明、定义和调用都使用相同的范围、类型和调用约定。

  • 当在类定义中包含函数原型,但不包含函数实现时调用的 C++ 代码。 若要解决此问题,请确保为调用的所有类成员提供定义。

  • 尝试从抽象基类调用纯虚函数。 纯虚函数没有基类实现。 若要解决此问题,请确保已实现所有调用的虚函数。

  • 尝试使用在该函数范围之外的函数(局部变量)中声明的变量。 若要解决此问题,请删除对不在范围中的变量的引用,或将该变量移动到更大的范围。

  • 生成 ATL 项目的发布版本时,会生成一条消息,指出需要 CRT 启动代码。 若要解决此问题,请执行以下任一操作,

    • 从预处理器定义列表中删除 _ATL_MIN_CRT 以允许包含 CRT 启动代码。 有关详细信息,请参阅“常规”属性页(项目)

    • 如果可能,请删除对需要 CRT 启动代码的 CRT 函数的调用。 相反,请使用其 Win32 等效项。 例如,请使用 lstrcmp 而不是 strcmp。 需要 CRT 启动代码的已知函数是一些字符串和浮点函数。

一致性问题

目前,在编译器供应商之间,甚至在同一编译器的不同版本之间尚不存在 C++ 名称修饰标准。 使用不同编译器编译的对象文件可能不使用相同的命名方案。 链接它们可能会导致错误 LNK2001。

在不同模块上混合使用内联和非内联编译选项可能会导致 LNK2001。 如果在打开函数内联(/Ob1 或 /Ob2)的情况下创建 C++ 库,但描述函数的相应头文件已关闭内联(无 inline 关键字),则会发生此错误。 若要解决此问题,请定义包含在其他源文件中的头文件中的 inline 函数。

如果使用 #pragma inline_depth 编译器指令,请确保已将值设置为 2 或更大,并确保还使用 /Ob1/Ob2 编译器选项。

如果在创建仅限资源 DLL 时省略 LINK 选项 /NOENTRY,则会发生此错误。 若要解决此问题,请将 /NOENTRY 选项添加到链接命令。

如果在项目中使用了不正确的 /SUBSYSTEM 或 /ENTRY 设置,则会发生此错误。 例如,如果编写控制台应用程序并指定 /SUBSYSTEM:WINDOWS,则会为 WinMain 生成未解析的外部错误。 若要解决此问题,请确保将选项与项目类型相匹配。 有关这些选项和入口点的详细信息,请参阅 /SUBSYSTEM/ENTRY 链接器选项。

导出的 .def 文件符号问题

当找不到 .def 文件中列出的导出时,会发生此错误。 这可能是因为导出不存在、拼写不正确或使用 C++ 修饰名。 .def 文件不采用修饰名。 若要解决此问题,请删除不需要的导出,并对导出的符号使用 extern "C" 声明。

使用修饰名查找错误

C++ 编译器和链接器使用名称修饰(也称为“名称重整”)。 名称修饰在其符号名称中对有关变量类型的额外信息进行编码。 函数的符号名称对其返回类型、参数类型、范围和调用约定进行编码。 此修饰名称是链接器搜索以解析外部符号的符号名称。

如果函数或变量的声明与函数或变量的定义并不完全匹配,则可能导致链接错误。 这是因为任何差异都会成为要匹配的符号名称的一部分。 即使在调用代码和定义代码中都使用相同的头文件,也可能发生此错误。 发生这种情况的一种方式是使用不同的编译器标志编译源文件。 例如,代码被编译为使用 __vectorcall 调用约定,但链接到需要客户端使用默认 __cdecl__fastcall 调用约定来调用它的库。 在这种情况下,符号不匹配,因为调用约定不同。

为了帮助你找到原因,错误消息会显示该名称的两个版本。 它同时显示“友好名称”(源代码中使用的名称)和修饰名(在括号中)。 无需了解如何解释修饰名。 仍然可以搜索并将其与其他修饰名进行比较。 命令行工具可帮助查找和比较预期的符号名称和实际的符号名称:

  • DUMPBIN 命令行工具的 /EXPORTS/SYMBOLS 选项在此处非常有用。 它们可帮助你发现 .dll 和对象或库文件中定义了哪些符号。 可以使用符号列表来验证导出的修饰名称是否匹配链接器搜索的修饰名称。

  • 在某些情况下,链接器只能报告符号的修饰名。 可以使用 UNDNAME 命令行工具获取修饰名称的未修饰形式。

其他资源

有关详细信息,请参阅堆栈溢出问题“什么是未定义的引用/未解析的外部符号错误以及如何修复此错误?”