程序中的标识符会发生什么?

时间:2009-12-31 18:29:02

标签: compiler-construction assembly linker

我是一名新手程序员。我只想看到不同阶段的输出编译,汇编和放大链接。我也不懂汇编语言。

我写了一个简单的程序

#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我对汇编语言的输出感到惊讶。我丢失了所有变量名称&amp;功能名称。

它保留了像人类,填充,主要的全局标识符,但它用下划线_作为前缀。所以,我不认为它使用标识符。无论如何,重点是丢失了所有标识符。

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

我对输出的更多阶段感到好奇,这些阶段将是二进制的(无法查看)。

组装后的输出怎么样?在链接之前?我猜它甚至会松散下划线前缀全局标识符?那么问题是如何调用函数或引用变量进行操作?

我在互联网上搜索信息但找不到任何有用的信息。可能我不知道该搜索什么。我不想读这本书的大书。但是如果有任何文章,教程清楚概念。这也会有所帮助。

我是一名新手程序员。因此,您可以用简单但技术性的术语来解释。

编辑:作为回应,评论。我把问题分成了几个问题。以下是此问题的第2部分:not clear with the job of the linker

4 个答案:

答案 0 :(得分:3)

在基本机器级别,没有更多名称,只有变量和代码的数字地址。因此,一旦您的代码被翻译成机器语言,名称就会用于实际目的。

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

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

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

答案 1 :(得分: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”的函数。

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

这与OS有很大不同。在许多操作系统中,链接器和加载都是一次性发生的。经常发生的是C编译器的输出被抛入存档而没有真正执行过多分辨率。

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

“程序没有运行,它刚刚处于制造阶段。”

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

“链接器如何映射到内存?它看起来怎么样?”

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

“它在运行时正确吗?”

这是调试的唯一方法 - 运行时间。它不能代表任何其他东西,或者它不是调试。

答案 2 :(得分:0)

要了解如何在汇编代码中处理局部变量,请编译如下内容:

int main() { int foo = 42; }

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

movq    %rsp, %rbp

将基指针设置为当前堆栈指针。然后:

movl    $42, -4(%rbp)

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

答案 3 :(得分:0)

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

这样可以了解.S是什么构成,以及什么是翻译成二进制文件。

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

有关链接的更多信息,请参阅优秀的免费链接器和装载程序预订http://www.iecc.com/linker/