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


当前回答

常量变量声明/定义中缺少“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变量

其他回答

因为当涉及到链接器错误时,人们似乎都在关注这个问题,所以我将在这里添加这个问题。

GCC 5.2.0中出现链接器错误的一个可能原因是现在默认选择了新的libstdc++库ABI。

如果您在std::__cxx11命名空间或标记[abi:cx11]中获得了有关符号未定义引用的链接器错误,则可能表明您正在尝试将使用_GLIBCXX_USE_cxx11_abi宏的不同值编译的对象文件链接在一起。这通常发生在链接到使用旧版本GCC编译的第三方库时。如果无法使用新的ABI重建第三方库,则需要使用旧的ABI重新编译代码。

因此,如果在5.1.0之后切换到GCC时突然出现链接器错误,这将是一件值得检查的事情。

班级成员:

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

声明析构函数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允许在类内初始化所有静态常量数据成员。

正在添加模板。。。

给定带有友元运算符(或函数)的模板类型的代码片段;

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

运算符<<被声明为非模板函数。对于与Foo一起使用的每种类型T,都需要有一个非模板运算符<<。例如,如果声明了一个类型Foo<int>,那么必须有如下的运算符实现:;

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

由于它未实现,链接器无法找到它并导致错误。

要更正此问题,可以在Foo类型之前声明一个模板运算符,然后将适当的实例化声明为友元。语法有点尴尬,但看起来如下:;

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

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

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

上述代码将运算符的友谊限制为Foo的相应实例化,即运算符<<<int>实例化被限制为访问Foo<int>的实例化的私有成员。

备选方案包括:;

允许友谊扩展到模板的所有实例化,如下所示;模板<typename T>Foo类{模板<typename T1>朋友std::ostream&operator<<(std::estream&os,const Foo<T1>/a);// ...};或者,运算符<<的实现可以在类定义内内联完成;模板<typename T>Foo类{朋友std::ostream&operator<<(std::estream&os,const Foo&a){ /*...*/ }// ...};

注意,当运算符(或函数)的声明仅出现在类中时,该名称不适用于“普通”查找,仅适用于cppreference中的依赖于参数的查找;

首先在类或类模板X中的友元声明中声明的名称将成为X最内部封闭命名空间的成员,但除非在命名空间范围内提供了匹配声明,否则无法进行查找(考虑X的依赖于参数的查找除外)。。。

cppreference和C++常见问题解答中有关于模板朋友的进一步阅读。

显示上述技术的代码列表。


作为失败代码示例的旁注;g++警告如下

警告:friend声明“std::ostream&operator<<(…)”声明非模板函数[-Wnon-template friend]注意:(如果这不是您想要的,请确保函数模板已经声明,并在此处的函数名称后面添加<>)

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

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

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

当我们在程序中引用了对象名(类、函数、变量等),并且链接器试图在所有链接的对象文件和库中搜索它时无法找到它的定义时,就会出现“Undefined Reference”错误。

因此,当链接器找不到链接对象的定义时,它会发出“未定义引用”错误。从定义中可以清楚地看出,这种错误发生在链接过程的后期。导致“未定义引用”错误的原因多种多样。

一些可能的原因(更频繁):

#1) 没有为对象提供定义

这是导致“未定义引用”错误的最简单原因。程序员只是忘记了定义对象。

考虑以下C++程序。这里我们只指定了函数的原型,然后在主函数中使用了它。

#include <iostream>
int func1();
int main()
{
     
    func1();
}

输出:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

因此,当我们编译此程序时,会发出链接器错误,该错误表示“未定义对‘func1()’的引用”。

为了消除这个错误,我们通过提供函数func1的定义来如下更正程序。现在程序给出了适当的输出。

#include <iostream>
using namespace std;
int func1();
 
int main()
{
     
    func1();
}
int func1(){
    cout<<"hello, world!!";
}

输出:

hello, world!!

#2) 使用的对象定义错误(签名不匹配)

“未定义引用”错误的另一个原因是我们指定了错误的定义。我们在程序中使用任何对象,其定义都不同。

考虑以下C++程序。这里我们调用了func1()。它的原型是int func1()。但其定义与原型不符。如我们所见,函数的定义包含函数的参数。

因此,当编译程序时,由于原型和函数调用匹配,编译是成功的。但是,当链接器试图将函数调用与其定义链接时,它会发现问题并将错误作为“未定义引用”发出。

#include <iostream>
using namespace std;
int func1();
int main()
{
     
    func1();
}
int func1(int n){
    cout<<"hello, world!!";
}

输出:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

因此,为了防止这种错误,我们只需交叉检查程序中所有对象的定义和用法是否匹配。

#3) 对象文件未正确链接

此问题还可能导致“未定义引用”错误。在这里,我们可能有多个源文件,我们可以独立编译它们。这样做时,对象链接不正确,导致“未定义引用”。

考虑以下两个C++程序。在第一个文件中,我们使用了第二个文件中定义的“print()”函数。当我们分别编译这些文件时,第一个文件为打印函数提供“未定义引用”,而第二个文件为主函数提供“不定义引用”。

int print();
int main()
{
    print();
}

输出:

main.cpp:(.text+0x5): undefined reference to 'print()'
collect2: error ld returned 1 exit status

int print() {
    return 42;
}

输出:

(.text+0x20): undefined reference to 'main'
collect2: error ld returned 1 exit status

解决此错误的方法是同时编译两个文件(例如,使用g++)。

除了已经讨论过的原因之外,“未定义的引用”也可能因为以下原因而发生。

#4) 错误的项目类型

当我们在visual studio等C++IDE中指定错误的项目类型,并尝试做项目不期望的事情时,我们就会得到“未定义的引用”。

#5) 没有库

如果程序员没有正确指定库路径,或者完全忘记指定它,那么我们将为程序从库中使用的所有引用获得一个“未定义引用”。

#6) 未编译从属文件

程序员必须确保我们事先编译项目的所有依赖项,以便在编译项目时,编译器找到所有依赖项并成功编译。如果缺少任何依赖项,那么编译器将给出“未定义的引用”。

除了上面讨论的原因之外,“未定义引用”错误还可能在许多其他情况下发生。但底线是程序员搞错了,为了防止这种错误,应该纠正这些错误。