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


当前回答

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

其他回答

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

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

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

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

编译时生成的所有对象文件显式或隐式的所有库(.lib)指定为此生成应用程序的附加依赖项。

此搜索过程称为解析。

D.最后,为什么是未解决的外部符号?如果链接器找不到内部没有定义的符号的外部定义,则会报告“未解决的外部符号”错误。

E.LNK2019的可能原因:未解决的外部符号错误。我们已经知道,此错误是由于链接器未能找到外部符号的定义所致,可能的原因如下:

定义已存在

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

int foo()
{
    return 0;
}

在b.cpp中,我们希望调用函数foo,因此我们添加

void foo();

要声明函数foo(),并在另一个函数体中调用它,请使用bar():

void bar()
{
    foo();
}

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

定义不存在

如果我们想调用库中的某些函数,但导入库没有添加到项目设置的附加依赖项列表中(设置自:项目|财产|配置财产|链接器|输入|附加依赖项)。现在链接器将报告LNK2019,因为当前搜索范围中不存在该定义。

班级成员:

纯虚拟析构函数需要实现。

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

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

这是因为在隐式销毁对象时调用基类析构函数,因此需要定义。

虚拟方法必须实现或定义为纯方法。

这类似于没有定义的非虚拟方法,增加了如下推理:纯声明会生成一个虚拟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() {}

如果实现在类定义之外,但在头中,则必须将方法标记为内联,以防止多重定义。

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

一个常见的错误是忘记限定名称:

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

可以为类定义中的整型或枚举类型的静态常量数据成员提供初始值设定项;然而,odr使用这个成员仍然需要如上所述的命名空间范围定义。C++11允许在类内初始化所有静态常量数据成员。

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文件,编译器在其中找不到一个符号,则编译器时间错误将引发消息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中已包含一个标头,则允许编译器不包含该标头确保外部库中不包含可能与头文件中定义的其他符号冲突的符号使用模板时,请确保在头文件中包含每个模板函数的定义,以允许编译器为任何实例化生成适当的代码。

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,请确保未设置定义,和/或在项目中使用了多字符设置,并始终应用。

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