7

谁能解释编译是如何工作的?

我似乎无法弄清楚编译是如何工作的..

更具体地说,这是一个示例。我正在尝试在 MSVC++ 6 中编写一些代码来加载 Lua 状态。

我已经:

  • 设置库的附加目录并将文件包含到正确的目录
  • 使用了 extern "C" (因为 Lua 只是 C 或者我听说的)
  • 包含正确的头文件

但是我仍然在 MSVC++6 中遇到一些关于未解析外部符号的错误(对于我使用的 Lua 函数)。

尽管我很想知道如何解决这个问题并继续前进,但我认为如果我了解所涉及的底层过程对我来说会更好,那么任何人都可以为此写一个很好的解释吗?我想知道的是过程..它可能看起来像这样:

第1步:

  • 输入:源代码
  • 过程:解析(也许在这里添加更多细节)
  • 输出:这里输出什么..

第2步:

  • 输入:步骤 1 输出的任何内容,以及可能需要的任何其他内容(库?DLL?.so?.lib?)
  • 过程:对输入所做的任何事情
  • 输出:输出什么

等等..

谢谢..

也许这将解释什么是符号,究竟是什么“链接”,什么是“对象”代码或其他什么......

谢谢。。对不起,我是个菜鸟。。

PS这不一定是特定语言的..但是请随意用您最熟悉的语言表达它.. :)

编辑:所以无论如何,我能够解决错误,事实证明我必须手动将 .lib 文件添加到项目中;简单地在 IDE 设置或项目设置中指定库目录(.lib 所在的位置)不起作用..

但是,下面的答案在一定程度上帮助我更好地理解了这个过程。非常感谢!.. 如果有人还想写一个详尽的指南,请做.. :)

编辑:仅供参考,我发现一位作者(Mike Diehl)的两篇文章很好地解释了这一点.. :) 检查编译过程:第 1 部分 检查编译过程:第 2 部分

4

5 回答 5

13

对于 C 语言和相关语言,从源代码到可执行文件通常是一个两阶段的过程,尽管 IDE 可能将其呈现为单个过程。

1/ 您编写源代码并通过编译器运行它。这个阶段的编译器需要你的源代码和你要链接的其他东西的头文件(见下文)。

编译包括将源文件转换为目标文件。目标文件有你的编译代码和足够的信息来知道他们需要什么其他东西,但不知道在哪里可以找到其他东西(例如,LUA 库)。

2/ 链接,下一个阶段,是将所有目标文件与库组合以创建可执行文件。我不会在这里介绍动态链接,因为这会使解释复杂化而没有什么好处。

您不仅需要指定链接器可以找到其他代码的目录,还需要指定包含该代码的实际库。您得到未解决的外部问题的事实表明您还没有这样做。

例如,考虑以下简化的 C 代码 ( xx.c) 和命令。

#include <bob.h>
int x = bob_fn(7);

cc -c -o xx.obj xx.c

这会将xx.c文件编译为xx.obj. bob.h包含原型,以便bob_fn()编译成功。指示编译器-c生成目标文件而不是可执行文件,并-o xx.obj设置输出文件名。

但实际代码bob_fn()不在头文件中,而是在 中,/bob/libs/libbob.so因此要链接,您需要类似以下内容:

cc -o xx.exe xx.obj -L/bob/libs;/usr/lib -lbob

xx.exexx.obj, 使用形式的库(在给定路径中搜索)创建libbob.so(lib 和 .so 通常由链接器添加)。在此示例中,-L设置库的搜索路径。如有必要,-l指定要查找以包含在可执行文件中的库。链接器通常采用“bob”并在-L.

库文件实际上是目标文件的集合(类似于 zip 文件如何包含多个其他文件,但不一定是压缩的) - 当找到第一个相关的未定义外部出现时,将从库中复制目标文件并添加到可执行文件,就像你的xx.obj文件一样。这通常会持续到没有更多未解决的外部因素。'relevant' 库是对 "bob" 文本的修改,它可能会查找libbob.a, libbob.dll, libbob.so, bob.a, bob.dll,bob.so等等。相关性由链接器本身决定,并应记录在案。

它的工作方式取决于链接器,但基本上就是这样。

1/ 你所有的目标文件都包含一个他们需要解决的未解决外部的列表。链接器将所有这些对象放在一起并修复它们之间的链接(解决尽可能多的外部问题)。

2/ 然后,对于每个仍未解析的外部,链接器梳理库文件,寻找可以满足链接的目标文件。如果它找到它,它会将其拉入 - 这可能会导致更多未解决的外部,因为拉入的对象可能有自己的需要满足的外部列表。

3/ 重复第 2 步,直到没有更多未解析的外部或无法从库列表中解析它们(这是您的开发所在,因为您没有包含 LUA 库文件)。

我之前提到的复杂性是动态链接。那是您链接例程的存根(某种标记)而不是实际例程的地方,后者稍后在加载时(当您运行可执行文件时)解决。诸如 Windows 通用控件之类的东西都在这些 DLL 中,因此它们可以更改,而无需将对象重新链接到新的可执行文件中。

于 2009-02-04T07:17:47.933 回答
5

第 1 步 - 编译器:

  • 输入:源代码文件[s]
  • 流程:解析源代码并翻译成机器码
  • 输出:目标文件[s],包括[s]:
    • 此对象中定义的符号名称,以及此对象文件“导出”的符号名称
    • 与此目标文件中定义的每个符号关联的机器代码
    • 未在该目标文件中定义但该目标文件中的软件所依赖且随后必须链接到的符号的名称,即该目标文件“导入”的名称

第 2 步 - 链接:

  • 输入:
    • 步骤 1 中的目标文件
    • 其他对象的库(例如来自操作系统和其他软件)
  • 过程:
    • 对于您要链接的每个对象
    • 获取此对象导入的符号列表
    • 在其他库中找到这些符号
    • 将相应的库链接到您的目标文件
  • 输出:单个可执行文件,其中包括所有对象的机器代码,以及导入(链接)到对象的库中的对象。
于 2009-02-04T07:10:50.880 回答
3

两个主要步骤是编译和链接。

编译采用单个编译单元(这些只是源文件,包含它们的所有头文件),并创建目标文件。现在,在这些目标文件中,在特定位置(地址)定义了很多函数(和其他东西,如静态数据)。在下一步,链接中,还需要一些关于这些函数的额外信息:它们的名称。所以这些也被存储了。单个对象文件可以引用实际上在其他对象文件中的函数(因为它想在代码运行时调用它们),但是由于我们在这里处理单个对象文件,因此只有符号引用(它们的“名称”)那些其他函数存储在目标文件中。

接下来是链接(让我们在这里限制自己的静态链接)。链接是将第一步中创建的目标文件(直接创建,或将它们一起放入 .lib 文件后)放在一起并创建可执行文件的地方。在链接步骤中,通过在正确的对象中查找名称、找到函数的地址并将地址放在正确的对象中,解析从一个对象文件或 lib 到另一个对象文件或 lib 的所有那些符号引用(如果可以的话)地方。

现在,解释一下你需要的'extern "C"'的东西:

C没有函数重载。一个函数总是可以通过它的名字来识别的。因此,当您将代码编译为 C 代码时,只有函数的真实名称存储在目标文件中。

然而,C++ 有一种叫做“函数/方法重载”的东西。这意味着函数的名称不再足以识别它。因此,C++ 编译器为包含函数原型的函数创建“名称”(因为名称加上原型将唯一标识一个函数)。这被称为“名称修改”。

当您想使用从 C++ 项目编译为“C”代码(例如,预编译的 Lua 二进制文件)的库时,需要“extern "C"”规范。

对于您的确切问题:如果它仍然不起作用,这些提示可能会有所帮助: * Lua 二进制文件是否使用相同版本的 VC++ 编译?* 你能简单地自己编译 Lua,无论是在你的 VC 解决方案中,还是作为 C++ 代码的单独项目?*你确定你所有的'extern "C"'都正确吗?

于 2009-02-04T07:16:31.463 回答
1

您必须进入项目设置并在“链接器”选项卡上的某处添加一个目录,其中包含该 LUA 库 *.lib 文件。设置称为“包括库”之类的,抱歉我无法查找。

您得到“未解析的外部符号”的原因是因为 C++ 中的编译分两个阶段进行。首先,代码被编译,每个 .cpp 文件在它自己的 .obj 文件中,然后“链接器”启动并将所有 .obj 文件连接到 .exe 文件中。.lib 文件只是一堆 .obj 文件合并在一起,使库的分发更简单一点。因此,通过添加所有“#include”和 extern 声明,您告诉编译器可以在某个地方找到具有这些签名的代码,但链接器找不到该代码,因为它不知道那些带有实际代码的 .lib 文件在哪里被放置。

确保您已阅读该库的 REDME,通常它们对您必须执行哪些操作才能将其包含在代码中进行了相当详细的解释。

于 2009-02-04T07:00:01.543 回答
1

您可能还想看看这个:编译器、汇编器、链接器和加载器:一个简短的故事

于 2009-06-08T20:45:26.207 回答