机器指令到汇编再到高级编程语言( 二 )


50年前的登月程序和程序员有多硬核 - https://coolshell.cn/articles/19612.html、
再附带一个宝藏网站(哥伦比亚大学出版的计算机历史,非常详细),有条件的同学 非常推荐 进去浏览一下:
http://www.columbia.edu/cu/computinghistory/index.html
汇编语言诞生CPU 的指令都是 二进制 的,这显然对于人类来说是 不可读 的 。为了解决二进制指令的可读性问题,工程师将那些指令写成了 八进制 。二进制转八进制是轻而易举的,但是八进制的可读性也不行 。
很自然地,最后还是用文字表达,加法指令写成 ADD 。内存地址也不再直接引用,而是用标签 表示 。
这样的话,就多出一个步骤,要把这些文字指令翻译成二进制,这个步骤就称为 assembling,完成这个步骤的程序就叫做 assembler 。它处理的文本,自然就叫做 aseembly code 。标准化以后,称为 assembly language,缩写为 asm,中文译为 汇编语言 。

机器指令到汇编再到高级编程语言

文章插图
 
  • 图片来源:http://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html
理解汇编语言每一种 CPU 的机器指令都是不一样的,因此对应的汇编语言也不一样 。本文介绍的是目前最常见的 x86 汇编语言,即 Intel 公司的 CPU 使用的那一种 。
寄存器要学习汇编语言,首先必须了解两个知识点:寄存器 和 内存模型 。
先来看寄存器 。CPU 本身只负责运算,不负责储存数据 。数据一般都储存在内存之中,CPU 要用的时候就去内存读写数据 。但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存 。基本上,CPU 缓存可以看作是读写速度较快的内存 。
但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度 。因此,除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据 。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据 。
机器指令到汇编再到高级编程语言

文章插图
 
寄存器不依靠地址区分数据,而依靠名称 。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的 。有人比喻寄存器是 CPU 的零级缓存 。
寄存器的种类早期的 x86 CPU 只有 8 个寄存器,而且每个都有不同的用途 。现在的寄存器已经有100 多个了,都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来 。
“EAXEBXECXEDXEDIESIEBPESP
上面这 8 个寄存器之中,前面七个都是通用的 。ESP 寄存器有特定用途,保存当前 Stack 的地址(详见下一节) 。
机器指令到汇编再到高级编程语言

文章插图
 
我们常常看到 32 位 CPU、64 位 CPU 这样的名称,其实指的就是寄存器的大小 。32 位 CPU 的寄存器大小就是 4 个字节 。
内存模型:Heap(堆)寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据 。所以,除了寄存器,还必须了解内存怎么储存数据 。
程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据 。这段内存有起始地址和结束地址,比如从 0x1000 到 0x8000,起始地址是较小的那个地址,结束地址是较大的那个地址 。
机器指令到汇编再到高级编程语言

文章插图
 
程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用 malloc 命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略) 。举例来说,用户要求得到 10 个字节内存,那么从起始地址 0x1000 开始给他分配,一直分配到地址 0x100A,如果再要求得到 22 个字节,那么就分配到 0x1020 。
机器指令到汇编再到高级编程语言

文章插图
 
这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆) 。它由起始地址开始,从低位(地址)向高位(地址)增长 。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收 。
内存模型:Stack(栈)除了 Heap 以外,其他的内存占用叫做 Stack(栈) 。简单说,Stack 是由于 函数运行而 临时占用 的内存区域 。


推荐阅读