编译示例

编译器(compiler)是将编程语言的代码转换为其他形式的软件,这种转换操作称为编译(compile)。

实际编译器有C语言的GCC(GNU Compiler Collection)、Java语言的编译器javac等。

编译示例

# 创建C语言程序
$ vim hello.c

#include <stdio.h>

int
main(int args, char **argv)
{
  printf("hello, world!\n");
  return 0;
}

# 生成名为hello的可执行文件(executable file)
$ gcc hello.c -o hello

# 运行生成的hello命令
$ ./hello
hello world!

疑问:

  • 可执行文件是这样的文件呢?
  • gcc命令是如何生成可执行文件的呢?
  • 可执行文件hello是经过哪些步骤运行起来的呢?

可执行文件

首先从GCC生成的可执行文件说起

现代的Linux上的可执行文件,通常指符合 ELF(Executable and Linking Format) 这种特定形式的文件。例如lscp这些命令(command)对应的实体文件都是可执行文件。

# Windows上查看可执行文件
$ file hello
hello: PE32 executable (console) Intel 80386, for MS Windows

# Linux上查看可执行文件
$ file hello

ELF文件包含了程序代码以及如何运行该程序的相关信息(元数据)。程序代码就是机器语言(machine language)的列表。机器语言是唯一一种CPU能够直接执行的语言,不同种类的CPU使用不同的机器语言。

GCC将C语言的程序转化为用机器语言描述的程序,将机器语言的程序按照ELF这种特定的文件格式注入文件,得到的就是可执行文件。

编译

gcc命令式如何将hello.c转换为可执行文件的呢?

hello.c 这样的单个文件来生成可执行文件时,虽然只需要执行一次 gcc 命令,但实际上其内部经历了如下 4 个阶段的处理。

  1. 预处理
  2. 狭义的编译
  3. 汇编
  4. 链接

上述处理统称为编译,严谨地说“狭义的编译”才是真正意义上的编译,这4个阶段的处理统称为 build

预处理

C语言的代码首先由预处理器(preprocessor)#include#define进行处理。

#include <stdio.h>

具体说来就是读入头文件,将所有的宏展开,这就是预处理(preprocess)preprocess是前处理的意思,其实就是在编译前进行处理。

狭义的编译

接着,编译器(compiler)对预处理器的输出进行编译,生成汇编语言(assemble language)的代码。一般来说,汇编语言的代码的文件扩展名为.s

汇编语言是由机器语言转换过来的人类较易阅读的文本形式的语言,机器语言是以CPU执行效率为第一要素设计的,用二进制代码表示,每个bit都有自己的含义,人类很难理解。因此,一般要使用与机器语言直接对应的汇编语言,以方便人类理解。

汇编

然后,汇编语言的代码由汇编器(assembler)转换为机器语言,这个处理过程称为汇编(assemble)

汇编器的输出称为目标文件(object file),一般来说,目标文件的扩展名为 .o

Linux中,目标文件也是ELF文件,既然都是ELF文件,那么究竟是目标文件还是可执行文件呢?这不是区分不了吗?这个不用担心。ELF文件中有用于提示文件种类的标志。

链接

目标文件本身还不能直接使用,无论是直接运行还是作为程序库(library)文件调用都不可以。将目标文件转换为最终可以使用的形式的处理称为链接(link)。使用程序库的情况下,会在这个阶段处理程序库的加载。

总结

build过程总结:C语言的代码经过预处理、编译、汇编、链接这4个阶段的处理,最终生成可执行文件。

4933701-eaea83cc285d9c7b.png
生成可执行文件的过程

程序运行环境

4933701-f114ba06753b4f98.png
程序运行的全过程

现代编程语言的运行过程中,运行环境所起的作用越来越大。

首先,链接的话题并非仅仅出现在build的过程中。如果使用共享库,那么在开始运行程序时,链接才会发生。最近广泛使用的动态加载(dynamic load),就是一种将所有链接处理放到程序运行时进行的手法。

其次,像Java和C#这种语言的运行环境中都有垃圾回收(GC, Garbage Collection)这一强大的功能,该功能对程序的运行有着很大的影响。

再次,在Java VM等具有代表性的Java的运行环境中,为了提高运行速度,采用了JIT编译器(Just In Time Compiler)。JIT编译器时在程序运行时进行处理,将程序转换为机器语言的编译器。也就是说,Java语言是在运行时进行编译的。

编程语言的运行方式

编译器会对程序进行编译,将其转换为可执行的形式。另外也有不进行编译,直接运行编译语言的方法。解释器(interpreter)就是这样的一个例子。解释器不将程序转换为别的语言,而是直接运行。例如Ruby和Perl的语言处理器就是用解释器来实现的。

运行语言的手段不止一种,例如C语言也可以用解释器来解释执行,Ruby也可以编译成机器语言或Java的二进制码。也就是说,编程语言与其运行方式可以自由搭配。因此,编译器也好,解释器也罢,都是处理并运行编程语言的手段之一,统称为编程语言处理器(programming language processor)

但是,根据语言的特点,其运行方式有适合、不适合该语言之说。一般来说,有静态类型检查(static type checking)、要求较高可靠性的情况下使用编译的方式;相反,没有静态类型检查、对灵活性的要求高于严密性的情况,则使用解释的方式。

静态类型检查是指在程序开始运行之前,对函数的返回值以及参数的类型进行检查的功能。与之相反,在程序运行过程中随时进行类型检查的方式称为动态类型检查(dynamic type checking)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值