常用寄存器对应表
寄存器 | 名字 | 含义 |
---|---|---|
EAX | Accumulator Register | 累加器寄存器 |
EBX | Base Register | 基址寄存器 |
ECX | Counter Register | 计数器寄存器 |
EDX | Data Register | 数据寄存器 |
ESI | Source Index | 来源索引 |
EDI | Destination Index | 目的地索引 |
EBP | Base Pointer | 基指针 |
ESP | Stack Pointer | 堆栈指针 |
寄存器名字的来历
[原作者:William Swanson]
[翻译:nicere]
挑选英特尔寄存器的艺术
我为一家名为Scene Zine的在线杂志写了这篇文章。Scene Zine迎合了Demo Scene,这是一个数字艺术社区,致力于通过音乐、艺术和计算机编程的混合来挑战计算机的极限。演示场景制作的一个特殊类别,即4K介绍,侧重于最终产品的原始文件大小。其目标是将尽可能多的高质量音乐、图形和动画放入仅有的4096字节中。要做到这一点需要高度专业化的尺寸优化技术,因为4096字节的空间比两页打字的文本或一个真彩色的Windows XP图标还要小。这篇文章讨论了其中的一些技术。
有些人评论说,他们希望在Scene Zine中看到更多的专家编程文章。为了纠正这种情况,这篇文章是为所有汇编语言程序员准备的。它讨论了在你的代码中选择使用哪些寄存器的精细艺术。这些信息应该可以简化你的编码,帮助你写出更小的程序。
当英特尔的工程师设计最初的8086处理器时,他们对每个寄存器都有一个特殊的目的。当他们设计指令集时,他们根据预期每个寄存器的功能创建了许多优化和特殊指令。根据英特尔最初的计划使用寄存器可以使代码充分利用这些优化。不幸的是,这似乎是一门失传的艺术。很少有编码员意识到英特尔的整体设计,而且大多数编译器都过于简单,或者专注于执行速度,无法正确使用寄存器。然而,了解寄存器和指令集是如何结合在一起的,是通向毫不费力地进行大小编码的重要一步。
除了优化大小之外,持续使用寄存器还有其他好处。就像使用好的变量名一样,使用一致的寄存器可以使代码更有可读性。当它们被正确使用时,寄存器的含义几乎和高级语言中的循环计数器i一样清晰。事实上,我偶尔会用x86寄存器来命名我的C语言变量,因为寄存器的名字非常有描述性。有了正确的寄存器使用,x86汇编几乎可以像高级语言一样自我记录。
一致的寄存器使用带来的另一个好处是更好的压缩。在使用压缩器来打包最终构建的产品中,例如4K介绍,创造更多的冗余代码会导致更小的打包尺寸。当代码一致地使用寄存器时,相同的指令序列就会重复出现。这反过来又会提高压缩率。
作为回顾,所有x86-家族的CPU都有8个通用的寄存器。寄存器的宽度为32位,尽管16位的版本也可以通过一个特殊的一字节指令前缀来访问。在16位模式下,情况是相反的。默认情况下,低16位可以访问,而完整的寄存器只能通过前缀字节访问。
每个寄存器的名称实际上是一个缩写。即使是 "字母 "寄存器EAX、EBX、ECX和EDX也是如此。下面的列表显示了寄存器的名称和它们的含义。
EAX - 累加器寄存器
EBX - 基址寄存器
ECX - 计数器寄存器
EDX - 数据寄存器
ESI - 来源索引
EDI - 目的地索引
EBP - 基指针
ESP - 堆栈指针
除了全尺寸的通用寄存器外,x86处理器还有8个字节大小的寄存器。由于这些寄存器直接映射到EAX、EBX、ECX和EDX,大多数人把它们看作是大寄存器的一部分。然而,从指令集的角度来看,这些8位寄存器是独立的实体。例如,CL和CH寄存器没有分享ECX寄存器的任何有用属性。除了AL和AH,其他的8位寄存器在指令集中没有任何特殊的意义,所以本文没有提到它们。
EAX: The Accumulator
有三种主要的处理器架构:寄存器、堆栈和累加器(register, stack, and accumulator)。在寄存器结构中,加法或减法等操作可以发生在任何两个任意的寄存器之间。在堆栈结构中,操作发生在堆栈的顶部和堆栈上的其他项目之间。在累加器结构中,处理器有一个称为累加器的计算寄存器。所有的计算都发生在累加器中,而其他寄存器作为简单的数据存储位置。
很明显,X86处理器没有累加器结构。然而,它确实有一个类似累加器的寄存器。EAX / AL。尽管大多数计算可以发生在任何两个寄存器之间,但指令集给予累加器作为计算寄存器的特殊优先权。例如,所有九个基本操作(ADD, ADC, AND, CMP, OR, SBB, SUB, TEST, 和 XOR)都有特殊的一字节操作码,用于在累加器和常数之间进行操作。特殊的操作,如乘法、除法、符号扩展和BCD校正,只能在累加器中进行。
由于大多数计算发生在累加器中,x86架构包含许多优化指令,用于将数据移入和移出该寄存器。首先,处理器有16个字节大小的XCHG操作码,用于在累加器和任何其他寄存器之间交换数据。这些指令并不十分有用,但它们显示了英特尔工程师对累加器比对其他寄存器有多么强烈的偏好。对他们来说,把数据换到累加器中,比在原地工作要好。其他将数据移入和移出累加器的指令有LODS, STOS, IN, OUT, INS, OUTS, SCAS, 和XLAT. 最后,MOV指令有一个特殊的单字节操作码,用于将数据从一个恒定的内存位置移入累加器。
在你的代码中,尽量在累加器中执行更多的工作。正如你所看到的,其余七个通用寄存器的存在主要是为了支持在累加器中进行的计算。
EDX: The Data Register
在剩下的七个通用寄存器中,数据寄存器EDX与累加器的关系最为密切。处理过大数据项的指令,如乘法、除法、CWD和CDQ,将最有意义的位存储在数据寄存器中,将最没有意义的位存储在累加器中。在某种意义上,数据寄存器是累加器的64位扩展。数据寄存器在IO指令中也起作用。在这种情况下,累加器持有要从端口读取或写入的数据,而数据寄存器持有端口地址。
在你的代码中,数据寄存器对存储与累加器的计算有关的数据最有用。根据我的经验,如果书写得当,大多数计算只需要这两个寄存器来存储。
ECX: The Count Register
计数寄存器ECX是x86中无处不在的循环变量i的等价物,x86中每个与计数有关的指令都使用ECX。最明显的计数指令是LOOP、LOOPZ和LOOPNZ。另一条基于计数器的指令是JCXZ,顾名思义,当计数器为0时就会跳转。 计数寄存器也出现在一些移位操作中,它持有要执行的移位次数。最后,计数寄存器通过REP、REPE和REPNE的前缀来控制字符串指令。在这种情况下,计数寄存器决定了操作的最大重复次数。
特别是在演示中,大多数计算都是在一个循环中发生的。在这种情况下,ECX是循环计数器的合理选择,因为没有其他寄存器围绕它有这么多的分支操作。唯一的问题是,这个寄存器是向下计数的,而不是像高级语言中那样向上计数。然而,设计一个向下计数并不难,所以这只是一个小困难。
EDI: The Destination Index
每个产生数据的循环都必须在内存中存储结果,这样做需要一个移动指针。目的索引,EDI,就是这个指针。目标索引持有所有字符串操作的隐含写入地址。值得注意的是,最有用的字符串指令是很少使用的STOS。STOS将数据从累加器复制到内存,并增加目标索引。这条单字节指令非常完美,因为任何计算的最终结果都应该在累加器中,而将结果存储在一个移动的内存地址中是一项常见的任务。
许多编码员把目标索引看作不过是额外的存储空间。这是个错误。所有的例程都必须存储数据,而一些寄存器必须作为存储指针。由于目标索引是为这项工作设计的,用它来做额外的存储空间是一种浪费。使用堆栈或其他的寄存器进行存储,并使用EDI作为你的全局写指针。
ESI: The Source Index
源索引,ESI,具有与目的索引相同的属性。唯一的区别是,源索引是用于读而不是写。尽管所有的数据处理程序都是写的,但并不是所有的都是读的,所以源索引并不是那么普遍的有用。然而,当需要使用它的时候,源索引和目的索引一样强大,并且有相同类型的指令。
当然,在你的代码不读取任何种类的数据的情况下,使用源索引以方便存储空间是可以接受的。
ESP and EBP: The Stack Pointer and the Base Pointer
在八个通用寄存器中,只有堆栈指针ESP和基数指针EBP被广泛用于其最初的用途。这两个寄存器是x86函数调用机制的核心。当一个代码块调用一个函数时,它把参数和返回地址推到堆栈上。一旦进入,函数将基础指针设置为等于堆栈指针,然后将自己的内部变量放在堆栈上。从那时起,函数就相对于基指针而不是堆栈指针来引用其参数和变量。为什么不是堆栈指针?由于某些原因,堆栈指针的寻址模式很糟糕。在16位模式下,它根本不可能是一个方括号的内存偏移。在32位模式下,它只能通过在操作码中添加一个昂贵的SIB字节来出现在方括号中。
在你的代码中,从来没有理由将堆栈指针用于堆栈以外的任何地方。然而,基点指针是可以使用的。如果你的例程通过寄存器而不是通过堆栈传递参数(他们应该这样做),就没有理由把堆栈指针复制到基本指针中。基准指针成为一个自由的寄存器,可以用于你需要的任何东西。
EBX: The Base Register
在16位模式下,基础寄存器EBX作为一个通用的指针。除了专门的ESI、EDI和EBP寄存器外,它是唯一可以出现在方括号内存访问中的通用寄存器(例如,MOV [BX], AX)。然而,在32位的世界中,任何寄存器都可以作为内存偏移,所以基数寄存器不再特殊。
基准寄存器的名字来自XLAT指令。XLAT使用AL作为索引,EBX作为基数,在表中查找一个值。XLAT相当于MOV AL, [BX+AL],如果你需要用表中的一个8位值替换另一个8位值,有时是很有用的(想想颜色查询)。
所以,在所有的通用寄存器中,EBX是唯一一个没有重要专用用途的寄存器。它是一个存储额外指针或计算步骤的好地方,但没有更多的用途。
总结
x86处理器系列中的八个通用寄存器都有一个独特的用途。每个寄存器都有特殊的指令和操作码,使实现这一目的更加方便或有效。下面简要介绍一下这些寄存器及其用途。
EAX - 所有主要的计算都在EAX中进行,因此它类似于一个专门的累加器寄存器。
EDX - 数据寄存器是对累加器的扩展。它对于存储与累加器当前计算有关的数据最为有用。
ECX - 和高级语言中的变量i一样,计数寄存器是通用的循环计数器。
EDI - 每个循环都必须将其结果存储在某个地方,目的索引指向那个地方。通过单字节的STOS指令将数据从累加器中写出来,这个寄存器使数据操作的大小更加有效。
ESI - 在处理数据的循环中,源索引持有输入数据流的位置。和目的索引一样,EDI也有一个方便的单字节指令,用于将数据从内存加载到累加器中。
ESP - ESP是神圣的堆栈指针。由于重要的PUSH、POP、CALL和RET指令都需要它的值,所以从来没有一个好的理由将堆栈指针用于其他方面。
EBP--在将参数或变量存储在堆栈上的函数中,基础指针持有当前堆栈帧的位置。然而,在其他情况下,EBP是一个自由的数据存储寄存器。
EBX - 在16位模式下,基数寄存器作为一个指针很有用。现在它是完全自由的额外存储空间。
下面是一个典型例程的概要,作为这些寄存器如何相互配合的例子。
点击查看代码
mov esi, source_address
mov edi, destination_address
mov ecx, loop_count
my_loop: lodsd
;Do some calculations with eax here.
stosd
loop my_loop
总而言之,按照英特尔的意图使用寄存器有几个好处。在第一种情况下,它可以使你的代码利用许多优化和特殊指令。它还可以使代码更易读,因为寄存器执行的是可预测的功能。最后,通过促进更多的重复性指令序列,持续使用寄存器会导致更好的压缩。
标签:常用,累加器,初略,索引,指令,寄存器,堆栈,指针 From: https://www.cnblogs.com/nicere/p/17030075.html