C和汇编程序实际编译的是什么?

lam*_*mas 47 c c++ compiler-construction assembly linker

所以我发现C(++)程序实际上并没有编译成简单的"二进制"(我可能在这里遇到了一些问题,在这种情况下我很抱歉:D)但是对于一系列事物(符号表) ,os相关的东西,...)但......

  • 汇编程序"编译"到纯二进制文件?这意味着除了预定义的字符串等资源之外没有额外的东

  • 如果C编译成除了普通二进制文件以外的其他内容,那么小型汇编程序引导程序如何只是将指令从HDD复制到内存并执行它们?我的意思是,如果操作系统内核(可能用C语言编写)编译成不同于普通二进制文件的东西 - 引导加载程序如何处理它?

编辑:我知道汇编程序没有"编译",因为它只有你的机器的指令集 - 我没有找到汇编程序"汇编"的好词.如果你有一个,请留在这里作为评论,我会改变它.

Nor*_*sey 47

C通常编译为汇编程序,只是因为这使得糟糕的编译器编写者的生活变得容易.

汇编代码总是汇编(而不是"编译")到可重定位目标代码.您可以将其视为二进制机器代码和二进制数据,但需要大量的装饰和元数据.关键部分是:

  • 代码和数据出现在命名的"部分"中.

  • 可重定位目标文件可以包括标签的定义,这些标签指的是部分内的位置.

  • 可重定位目标文件可以包括要用其他地方定义的标签值填充的"空洞".这样一个洞的官方名称是搬迁入口.

例如,如果您编译和汇编(但不链接)此程序

int main () { printf("Hello, world\n"); }
Run Code Online (Sandbox Code Playgroud)

你最终可能会找到一个可重定位目标文件

  • text含有用于机器代码部main

  • 标签定义main,指向文本部分的开头

  • rodata包含字符串文字的字节(只读数据)部"Hello, world\n"

  • 一个重定位条目,它依赖于printf并指向文本部分中间的调用指令中的"洞".

如果您在Unix系统上,可重定位目标文件通常称为.o文件,hello.o您可以使用一个简单的工具来探索标签定义和使用nm,您可以从更复杂的工具中获取更详细的信息叫objdump.

我教了一个涵盖这些主题的课程,我让学生编写汇编程序和链接器,这需要几个星期,但是当他们完成时,大多数人都可以很好地处理可重定位目标代码.这不是一件容易的事.

  • 可重定位代码不是C的要求,许多平台不使用它. (7认同)
  • 大多数C编译器直接编译为可重定位的机器代码。跳过缓慢的文本步骤会更快。某些文件(例如具有.COM文件功能的16位编译器)可以直接生成不可重定位的代码。有人可能会争辩说,在直接由机器代码生成的编译器中,汇编器是一个相对独立的常规部分。 (2认同)
  • @Lothar我的课程在线http://www.cs.tufts.edu/comp/40.过去几年,请访问我的主页.由于显而易见的原因,答案不在线. (2认同)

Pau*_*han 37

我们来一个C程序吧.

当您在c程序上运行'gcc'或'cl'时,它将经历以下阶段:

  1. 预处理器lexing(#include,#ifdef,trigraph分析,编码翻译,评论管理,宏......)
  2. 词法分析(产生令牌和词汇错误).
  3. 句法分析(产生解析树和语法错误).
  4. 语义分析(生成符号表,作用域信息和作用域/键入错误).
  5. 输出到汇编(或其他中间格式)
  6. 装配优化(如上所述).可能仍然在ASM字符串中.
  7. 将程序集组装成一些二进制对象格式.
  8. 需要将程序集链接到任何静态库,以及重新定位它.
  9. 以elf或coff格式输出最终可执行文件.

实际上,其中一些步骤可能同时完成,但这是逻辑顺序.

请注意,实际可执行二进制文件周围有一个elf或coff格式的"容器".

你会发现一本关于编译器的书(我推荐书,该领域的标准入门书)将拥有你需要的所有信息以及更多.

正如Marco评论的那样,链接和加载是一个很大的区域,龙书或多或少停止在可执行二进制文件的输出.实际上从那里开始运行在操作系统上是一个相当复杂的过程,Levine在连接器和装载器中 涵盖了这个过程.

我维基这个答案让人们调整任何错误/添加信息.

  • 嗯,龙书主要是关于解析.我推荐Levine的"Linkers and Loaders",http://www.iecc.com/linker/,也可以在网上找到. (5认同)

Tho*_*ews 18

将C++转换为二进制可执行文件有不同的阶段.语言规范没有明确说明翻译阶段.但是,我将描述常见的翻译阶段.

源C++汇编语言或中级语言

有些编译器实际上将C++代码翻译成汇编语言或中间语言.这不是必需的阶段,但有助于调试和优化.

装配到目标代码

下一个常见步骤是将汇编语言转换为对象代码.目标代码包含具有相对地址的汇编代码和对外部子例程(方法或函数)的开放引用.通常,翻译器尽可能多地将信息输入到目标文件中,其他一切都未解析.

链接目标代码

链接阶段组合一个或多个目标代码,解析引用并消除重复的子例程.最终输出是可执行文件.此文件包含操作系统和相关地址的信息.

执行二进制文件

操作系统通常从硬盘驱动器加载可执行文件,并将其放入内存.OS可以将相对地址转换为物理位置.OS还可以准备可执行文件所需的资源(例如DLL和GUI小部件)(可以在可执行文件中声明).

直接编译为二进制文件有些编译器(如嵌入式系统中使用的编译器)可以直接从C++编译为可执行的二进制代码.此代码将具有物理地址而不是相对地址,并且不需要加载OS.

好处

这些阶段的优点之一是C++程序可以分解成碎片,单独编译并在以后链接.它们甚至可以与其他开发人员(也称为库)的片段链接.这允许开发人员仅在开发中编译片段并链接已经验证的片段.通常,从C++到对象的转换是该过程的耗时部分.此外,当源代码中存在错误时,人们不希望等待所有阶段完成.

保持开放的心态,并始终期待第三种选择(选项).


t0m*_*13b 5

为了回答您的问题,请注意,这是主观的,因为有不同的处理器、不同的平台、不同的汇编器和 C 编译器,在这种情况下,我将讨论 Intel x86 平台。

  1. 汇编器通常不会汇编为纯/平面二进制(原始机器代码),而是通常汇编为由数据、文本和 bss 等段定义的文件;这称为目标文件。链接器介入并调整段以使其可执行,即准备运行。顺便说一句,使用 GNU 汇编时的默认输出as foo.sa.out,它是汇编器输出的简写。(但链接器输出的 gcc 默认文件名相同,而汇编器输出只是临时的。)
  2. 引导加载程序定义了一个特殊的指令,早在 DOS 时代,就很常见诸如 之类的指令.Org 100h,它在 .EXE 流行之前将汇编代码定义为旧的 .COM 类型。此外,您不需要汇编器来生成 .COM 文件,使用 MSDOS 附带的旧 debug.exe,可以完成小型简单程序的技巧,.COM 文件不需要链接器并且直接准备就绪 -运行二进制格式。这是一个使用 DEBUG 的简单会话。
1:*0100
2:* 移动 AH,07
3:* 整数 21
4:* CMP AL,00
5:* jnz 010c
6:* 移动 AH,07
7:* 整数 21
8:* 移动 AH,4C
9:* 整数 21
10:*
11:*r CX
12:*10
13:*n respond.com
14:*w
15:*q

这会生成一个名为“respond.com”的可立即运行的 .COM 程序,该程序等待击键而不将其回显到屏幕上。请注意,开头使用了“a 100h”,它表明指令指针从 100h 开始,这是 .COM 的特征。这个旧脚本主要用于批处理文件中等待响应而不是回显它。原始脚本可以在这里找到。

同样,对于引导加载程序,它们会转换为二进制格式,DOS 中曾经有一个程序,称为EXE2BIN。这项工作是将原始目标代码转换为可以复制到可启动磁盘上进行启动的格式。请记住,没有链接器针对汇编的代码运行,因为链接器用于运行时环境并设置代码以使其可运行和可执行。

BIOS 启动时,预计代码位于段:偏移量,0x7c00,如果我的记忆正确,代码(在 EXE2BIN 后)将开始执行,然后引导加载程序将其自身重新定位在内存中较低的位置并继续加载发出 int 0x13 从磁盘读取,打开 A20 门,启用 DMA,由于 BIOS 处于 16 位模式而切换到保护模式,然后从磁盘读取的数据加载到内存中,然后引导加载程序发出远跳转到数据代码中(可能用 C 编写)。这本质上就是系统启动的方式。

好吧,上一段听起来很抽象和简单,我可能错过了一些东西,但简而言之就是这样。