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


当前回答

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

其他回答

我在头文件中声明函数的原型时遇到了这个问题:

int createBackground(VertexArray rVA,IntRect arena);

但随后使用具有第一个参数的引用在其他地方定义函数:

int createBackground(VertexArray&rVA,IntRect arena){}

原型没有在第一个参数中使用引用,而定义是,这一事实导致了这个问题。当我将两者都更改为正确匹配包含引用或不包含引用时,问题得到了解决。

干杯

函数或类方法在源文件中使用内联说明符定义。

例如:-

主.cpp

#include "gum.h"
#include "foo.h"

int main()
{
    gum();
    foo f;
    f.bar();
    return 0;
}

foo.h(1)

#pragma once

struct foo {
    void bar() const;
};

口香糖.h(1)

#pragma once

extern void gum();

foo.cpp(1)

#include "foo.h"
#include <iostream>

inline /* <- wrong! */ void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp(1)

#include "gum.h"
#include <iostream>

inline /* <- wrong! */ void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

如果指定gum(类似地,foo::bar)在其定义中是内联的,那么编译器将通过以下方式内联gum(如果它选择):-

没有任何独特的口香糖定义,因此不发出任何符号,链接器可以通过该符号引用口香糖的定义,而是将所有对gum的调用替换为编译后的gum主体的内联副本。

因此,如果在源文件gum.cpp中内联定义gum,则编译为对象文件gum.o,其中所有对gum的调用都是内联的并且没有定义接头可以指代口香糖的符号。当你将gum.o与另一个对象文件(例如main.o)链接到程序中引用外部符号gum时,链接器无法解析这些参考文献。因此连杆失效:

编译:

g++ -c  main.cpp foo.cpp gum.cpp

链接:

$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status

如果编译器可以在调用gum的每个源文件中看到它的定义,则只能将gum定义为内联。这意味着它的内联定义需要存在于包含在每个源文件中的头文件中您可以在其中调用gum。做两件事之一:

要么不内联定义

从源文件定义中删除内联说明符:

foo.cpp(2)

#include "foo.h"
#include <iostream>

void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp(2)

#include "gum.h"
#include <iostream>

void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

重新生成:

$ g++ -c  main.cpp foo.cpp gum.cpp
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const

成功

或正确内联

头文件中的内联定义:

foo.h(2)

#pragma once
#include <iostream>

struct foo {
    void bar() const  { // In-class definition is implicitly inline
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};
// Alternatively...
#if 0
struct foo {
    void bar() const;
};
inline void foo::bar() const  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif

口香糖.h(2)

#pragma once
#include <iostream>

inline void gum() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

现在我们不需要foo.cpp或gum.cpp:

$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const

链接的.lib文件与.dll关联

我也有同样的问题。假设我有项目MyProject和TestProject。我已经有效地将MyProject的lib文件链接到TestProject。然而,此lib文件是在构建MyProject的DLL时生成的。此外,我没有包含MyProject中所有方法的源代码,只包含对DLL入口点的访问。

为了解决这个问题,我将MyProject构建为LIB,并将TestProject链接到此.LIB文件(我将生成的.LIB文件复制粘贴到TestProject文件夹中)。然后我可以再次将MyProject构建为DLL。它正在编译,因为TestProject链接到的库确实包含MyProject中类中所有方法的代码。

当我们在程序中引用了对象名(类、函数、变量等),并且链接器试图在所有链接的对象文件和库中搜索它时无法找到它的定义时,就会出现“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) 未编译从属文件

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

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

需要考虑的一些拼写错误:(我作为初学者经常遇到)

如果您使用的是类:请检查您是否没有在定义函数的cpp文件中的函数名之前忘记“classname::”。如果使用forward声明:请确保声明正确的类型。例如:如果要转发声明“结构”,请使用“结构”而不是“类”。