2

我正在尝试使用 SFML 库来了解 C++ 开发。我正在学习教程(http://www.gamefromscratch.com/page/Game-From-Scratch-CPP-Edition-Part-7.aspx),并使用 Visual Studio 2010。

我一直遇到的一个关于未解决的外部问题的问题。我真的在为此苦苦挣扎,因为与我遇到的大多数错误不同,它似乎 a) 与代码没有任何关系,并且 b) 行为不一致。与其给你们一个具体的例子并寻求帮助来解决这个例子,我希望开发一种更可靠的方法来解决这些问题。不过,我会给你一个常见情况的概述。

我有一个解决方案,其中包含 8 个头文件和对应于它们的 8 个 cpp 文件。解决方案是稳定的:它编译和运行没有错误或警告。

我将进入一个头文件并添加这一行:

虚空 DoNothing();

然后我将进入匹配的 cpp 文件并编写方法:

无效 DoNothing(){};

我编译并运行,得到 5 个未解决的外部错误。它们没有指向任何代码行,所以我真的不知道如何修复它们,但我显然做错了什么。很公平。为了回到稳定状态,我删除了我插入的两行代码,然后编译。即使代码与最后一个稳定状态相同,我也会得到相同的未解决的外部错误。

尝试随机的事情,我进入另一个 cpp 文件并颠倒两个包含的头文件的顺序。游戏现在编译。如果我将包含的头文件的顺序切换回来,它就会编译。

什么是未解决的外部错误?为什么他们的行为似乎与我输入的代码不一致?我如何阅读它们以找出问题所在,以及如何首先避免它们?

谢谢你。

ps:如果我应该提供更具体的细节,请告诉我。

4

2 回答 2

1

“未解决的外部”错误意味着您的代码引用了不存在的东西(通常是函数或方法,但也可以是变量)。这些是链接错误,而不是编译错误;这就是为什么您没有得到行号和更有用的错误消息的原因。

让我给你一些关于如何将 C++ 代码转换为可执行文件的背景知识(请记住,我正在简化一些事情。)

项目中的每个 C++ 源文件(而不是头文件)都是单独编译的。“.cpp”文件和它包含的所有头文件都被编译成所谓的目标文件目标代码。(这些文件具有“.obj”或“.o”扩展名。)您还可以将库文件(即 Windows 上的“.lib”文件和 Linux 上的“.a”文件)视为这些目标文件的集合, 保存以备后用。

为了生成可执行程序(例如 Windows 上的 EXE 或 DLL 文件),所有这些目标文件都链接在一起,

现在,重要的是每个源文件都是独立编译的,并且独立于其他源文件。因此,如果一个文件中的代码调用了另一个文件中实现的函数,编译器将看不到该函数的实际主体,并且只能假设只要被调用函数的声明是可见的(即原型,即您在标题中编写的行,)然后这些文件最终将链接在一起,并将离开实际调用链接器的任务。这通常意味着只要您包含正确的标头,您的编译器就会很高兴。

但是链接器将更加顽固和迂腐。在链接时,您确实需要提供您在整个项目中使用的所有功能的主体(即实现)。您的任务是确保所有正确的目标文件和库都链接在一起,并且每个使用的函数的实现在它们之间的某个地方恰好存在一次(不多也不少。)

这给我们带来了您的问题。当您收到“未解决的外部”链接器错误时,这意味着您调用的函数的主体在您链接在一起的目标文件和库中的任何位置都不存在。

显然,正在发生两件事之一。要么你已经包含了一个外部库的头文件,但是忘记了链接库文件本身(这不是你的问题)或者你已经声明了一个函数(即为它编写了原型)但忘记了实现它的主体.

请记住,链接器在这里非常严格。如果您在课堂上声明这样的内容:

class Foo {
    void bar (int x);
};

然后在你的“.cpp”文件中,实现这个功能:

void bar (int x)
{
    // Do nothing
}

那么如果您实际上Foo::bar()在程序中的任何地方调用,您将得到一个未解决的外部错误,因为已实现bar()的方法不是Foo(您应该已经实现void Foo::bar (int x) {})。如果您稍微拼写错误或参数类型错误或诸如此类,也会发生类似的事情.

阅读链接器错误并从中理解可能很困难。有时,链接器抱怨的名称(它说它找不到的“符号”)都被破坏得面目全非。这与 *Application Binary Interface*s (ABI) 和几十年的历史和优先级有关。无论如何,大多数时候,如果您仔细查看链接错误消息,您可以看到函数名称并检查您的代码(或库)并重试。

此外,虽然这种情况很少见,但有时为了解决某些链接问题,您必须求助于完全重建您的项目。

于 2013-04-14T19:45:02.960 回答
0

每次我看到这样的行为都是因为项目之间的循环引用。例如,项目 A 引用了项目 B 中实现的对象/符号,同时项目 B 引用了项目 A 中的对象/符号。每次构建解决方案时,工具都必须编译一个项目首先,然后是另一个。如果您对要编译的第二个项目进行更改,则第一个在第一轮编译中看不到更改,并且构建失败。如果您设法手动构建项目 B(针对现在已过时的库 B 副本),则解决方案开始正确构建。更复杂的循环是可能的(例如,A 取决于 B,B 取决于 C,C 取决于 A)。您没有明确提及多个项目,但我敢打赌您拥有它们。

这些循环引用在已经存在很长时间并且随着时间的推移缓慢增长的大型解决方案中很常见。人们习惯于添加从所有事物到所有事物的链接,因为他们需要这里的一个函数,那里的结构......

寻找这些依赖关系。您应该能够从源代码进行完全干净的重建。你的依赖树应该看起来像……嗯,一棵树;不是图表。

于 2013-04-14T22:28:37.160 回答