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


当前回答

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

其他回答

对的未定义引用WinMain@16或类似的“不寻常”main()入口点引用(特别是对于visual studio)。

您可能错过了使用实际IDE选择正确的项目类型。IDE可能希望将例如Windows应用程序项目绑定到这样的入口点函数(如上面缺失的引用中所指定的),而不是通常使用的int main(int argc,char**argv);签名

如果IDE支持普通控制台项目,您可能希望选择此项目类型,而不是windows应用程序项目。


下面是从真实世界问题中更详细地处理的案例1和案例2。

使用链接器帮助诊断错误

大多数现代链接器都有一个冗长的选项,可以在不同程度上打印出来;

链接调用(命令行),关于链接阶段中包括哪些库的数据,库的位置,使用的搜索路径。

用于gcc和clang;通常会在命令行中添加-v-Wl、--verbose或-v-Wl和-v。更多详情请点击此处;

Linux ld手册页。LLVM链接器页。“GCC简介”第9章。

对于MSVC,/VERBOSE(特别是/VERBOSE:LIB)将添加到链接命令行。

/VERBOSE链接器选项上的MSDN页面。

我的例子:

头文件

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

.cpp文件:

vector<GameCharacter*> characterList;

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

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

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

vector<GameCharacter*> GameCharacter::characterList;

同时保持收割台相同。

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

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

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

不支持链接器脚本的GNUld包装器

一些.so文件实际上是GNU ld链接器脚本,例如libtbb.so文件是一个ASCII文本文件,其内容如下:

INPUT (libtbb.so.2)

一些更复杂的构建可能不支持这一点。例如,如果在编译器选项中包含-v,则可以看到mainwin gcc包装器mwdip丢弃要链接的库的详细输出列表中的链接器脚本命令文件。

cp libtbb.so.2 libtbb.so

或者可以用.so的完整路径替换-l参数,例如,代替-ltbb-do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtb.so.2