【问题标题】:What happens to identifiers in a program?程序中的标识符会发生什么?
【发布时间】:2010-12-31 11:43:28
【问题描述】:

我是一个新手程序员。我只是想在编译、组装和链接的不同阶段查看输出。我也不懂汇编语言。

我写了一个简单的程序

#include <stdio.h>

int humans = 9;

 int main() 
 {
        int lions = 2;
        int cubs = populate(lions);
        return 0;
 }

 int populate(int crappyVariable)
 {
    return ++crappyVariable;
}

我使用了gcc - S sample.c 我对汇编语言的输出感到惊讶。我丢失了所有变量名和函数名。

它保留了全局标识符,如human、populate、main,但它在它们前面加上下划线_。因此,我不会将其视为使用标识符。无论如何,关键是它丢失了所有标识符。

我的问题是它如何调用函数或引用变量?

我真的很好奇输出的进一步阶段,它将是二进制的(不可见)。

组装后和链接前的输出如何?我想它也会丢失下划线前缀的全局标识符吗?那么问题又是如何调用函数或引用变量进行操作?

我在互联网上搜索了信息,但找不到任何有用的信息。可能是我不确定要搜索什么。我不想读这方面的大书。但是,如果有任何文章,教程,其中明确的概念。这也会很有帮助。

我是一个新手程序员。因此,如果您能用简单但技术性的术语进行解释,那就太好了。

编辑:作为回应,对评论。我把我的问题分解成多个问题。这是这个问题的第二部分:not clear with the job of the linker

【问题讨论】:

  • 如果你把它分成不同的问题,你可能会有更好的运气。在一个答案中涵盖这么多领域可能比大多数人(包括我)在业余时间愿意做的更多。
  • 对不起。我没有想到。我很困惑。于是只好继续写。我会尝试分成多个帖子。

标签: compiler-construction assembly linker


【解决方案1】:

在基本机器级别,不再有名称,只有变量和代码的数字地址。因此,一旦您的代码被翻译成机器语言,这些名称就不再适用了。

如果您使用“to assembler”选项进行编译或反汇编代码,您可能会看到一些标识符;它们可以帮助您找到解决代码的方法,因为您不会不必要地在头脑中计算数据/代码偏移量。

回答您关于链接等的问题:一旦程序编译为可重定位对象形式,仅在 C 程序文件“内部”使用的标签和标识符就会消失。但是,需要外部定义的名称,例如main(),因为外部模块会引用它们;所以编译的目标文件将包含一个小表格,列出外部可见的名称以及它们所指的位置。然后,链接器可以根据这些名称将外部引用从其他人(反之亦然)修补到您的模块中。

链接后,即使是外部定义的名称也不再需要。但是,如果您使用调试选项进行编译,名称表可能仍会附加到最终程序中,因此您可以在调试程序时使用这些名称。

【讨论】:

    【解决方案2】:

    您确实需要阅读编译器和编译器设计。 以http://www.freetechbooks.com/compiler-design-and-construction-f14.html开头

    这是摘要。

    目标是将内容复制到将执行和运行的内存中。然后操作系统将控制权交给那些东西。

    “加载器”将各种文件中的内容复制到内存中。这些文件实际上是一种语言,描述了内容在内存中的位置以及这些位置中的内容。这是一种“加载记忆”的语言。

    编译器和链接器的工作是创建使加载程序执行正确操作的文件。

    编译器的输出是“目标”文件——本质上是许多带有许多外部引用的小碎片文件中的加载器指令。理想情况下,编译器的输出是一些机器代码,其中包含要插入的外部引用的占位符。所有内部引用都已解析为堆内存或堆栈帧或函数名称的偏移量。

    链接器的输出是更大的加载器文件,外部引用更少。它在格式上与编译器的输出基本相同。但它包含了更多的东西。

    阅读 ld 命令:http://linux.about.com/library/cmd/blcmdl1_ld.htm

    在 nm 命令上阅读此内容:http://linux.about.com/library/cmd/blcmdl1_nm.htm

    这里有一些细节。

    "...它如何调用函数或引用变量?"

    函数名称通常会保留到产生输出的后期阶段。

    变量名被转换成别的东西。 “全局”变量是静态分配的,编译器有一个从变量名到类型到静态(“堆”)内存偏移量的映射。

    函数中的局部变量(通常)分配在堆栈帧中。编译器有一个从变量名到类型到堆栈帧偏移量的映射。进入函数时,会分配所需大小的堆栈帧,变量只是在该帧中的偏移量。

    "...它将如何调用函数或引用变量进行操作?"

    您必须向编译器提供提示。 extern 关键字告诉编译器名称未在此模块中定义,而是在另一个模块中定义,并且必须在链接(或加载)时解析引用。

    “...如果没有可链接的内容...”

    这绝不是真的。您的程序只是整个可执行文件的一部分。大多数 C 库都包含 real 主程序,然后它会调用名为“main”的函数。

    “链接器会改变汇编器的目标代码输出吗?”

    这因操作系统而异。在许多操作系统中,链接器和加载都同时发生。经常发生的情况是,C 编译器的输出在没有真正执行太多解析的情况下被放入存档中。

    当可执行文件被加载到内存中时,归档引用和任何外部共享对象文件也会被加载。

    “程序没有运行,它只是在制造阶段。”

    这并不意味着什么。不知道你为什么要包括这个。

    “链接器如何映射到内存?它会是什么样子?”

    操作系统将分配一块内存,可执行程序必须复制到该内存块中。链接器/加载器读取目标文件、任何目标存档文件,并将这些文件中的内容复制到该内存中。链接器进行复制和名称解析,并编写一个新的目标文件,它更适合编译器。加载器将其放入实际内存并将执行移交给生成的文本页面。

    “它在运行时对吗?”

    这是调试的唯一方法——运行时。不能有别的意思,或者不是在调试。

    【讨论】:

    • "程序没有运行,它只是在制造阶段。"我的意思是我写的c程序还没有进入执行阶段。它仍在成为可执行文件的过程中
    • 好吧,您的文件要么是源代码文件,要么已编译为目标代码文件,要么已链接到可执行文件中。
    • @Alice:这仍然无关紧要;所有这些都发生在您的程序开始运行之前很久。说它没有运行是没有帮助的。
    【解决方案3】:

    要查看在汇编代码中如何处理局部变量,请编译如下代码:

    int main() { int foo = 42; }
    

    您会注意到的不仅是变量名消失了,而是结果数据在哪里消失了。你会看到类似的东西:

    movq    %rsp, %rbp
    

    它将基指针设置为当前堆栈指针。那么:

    movl    $42, -4(%rbp)
    

    因此,这告诉我们编译器在堆栈上分配了一些空间,但未命名。添加更多变量如 foo 基本上只会在基指针下分配更多内存。原来是“foo”的变量现在只是 -4(%rbp)

    【讨论】:

      【解决方案4】:

      下一步是对生成的 .o 运行 objdump -D 并将其与 .S 版本进行比较。

      这让我们知道 .S 是什么构成,什么被翻译成二进制。

      最后阶段是链接,这大致意味着两次传递将多个 .o 文件之间的所有标签解析为相对于 0 的地址或加载地址。

      有关链接的更多信息,请参阅伟大的免费链接器和加载器书籍 http://www.iecc.com/linker/

      【讨论】:

      • 这是一个很好的建议。我一定会做到的。我想了解更多关于这些东西的信息。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-22
      • 1970-01-01
      • 2012-12-05
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多