[英]Is it necessary or easier to code x86 assembly by following the purpose of each General Purpose register
一般来说,按照每个寄存器的用途编写 x86 汇编代码是否必要或更容易?
x86 架构中的寄存器最初被设计为具有特殊用途,但现代编译器似乎并不关心它们的使用(除非在某些特殊条件下,例如 REP MOV 或 MUL)。
那么,根据每个寄存器的用途,代码会更容易或更优化吗?(不管与某些寄存器相同的特殊指令(或编码)如何)
例如(我可以使用 REP MOVSB 或 LODSB STOSB 代替,但只是为了演示):
第一个代码:
LEA ESI,[AddressOfSomething]
LEA EDI,[AddressOfSomethingElse]
MOV ECX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ESI]
ADD AL,8
MOV [EDI],AL
ADD ESI,1
ADD EDI,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...
第二个代码:
LEA ECX,[AddressOfSomething]
LEA EDX,[AddressOfSomethingElse]
MOV EBX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ECX]
ADD AL,8
MOV [EDX],AL
ADD ECX,1
ADD EDX,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...
我使用的编译器——Visual Studio 2015 在执行此类任务时通常使用第二种方法,它不使用寄存器取决于其目的,相反,编译器仅根据其“易失性”选择要使用的寄存器或“非易失性”特性(调用函数后)。 正因为如此,所有的高级编程语言编程的软件反汇编都使用第二种方法。
另一个有趣的事实是,在 ARM 语言中,GPR 都具有相同的用途,并被命名为 R0-R7,这意味着使用它进行编码时,代码将更类似于第二个代码。
总而言之,我认为这两个代码使用相同的指令,因此无论我使用什么寄存器,它都应该具有相同的速度。 但我说得对吗? 哪个代码更容易编码?
遵循各个寄存器的目的主要达到:
代码密度
例如,使用A
寄存器1通常会减少移动、算术、逻辑和 IO 2等常见操作的代码大小。
使用C
寄存器进行计数可让您利用jcxz
系列指令,避免显式比较。
movsd
和类似的指令是非常“密集”的指令,它们执行复杂的操作,否则将需要大量代码。
然而,代码密度并不意味着“更快”,因为 x86 公然是 CISC,一个复杂的指令可能比等效的一系列更简单的指令3需要更多的时间来执行。
可读性
像rep movsd
这样的指令实际上是一种编码循环的“高级”方式,该循环将数据从源移动到目标。
解析循环
push eax pushf .loop: mov eax, DWORD [esi] mov DWORD [es:edi], eax add esi, 4*(1-D*2) add edi, 4*(1-D*2) dec ecx jnz .loop popf pop eax
要困难得多。
惯用编程
许多指令( call
、 ret
、 push
……)都假定使用SP
作为堆栈指针。
可以避免使用SP
作为堆栈指针,但它不会很惯用(也不高效)。
更少的数据移动
在实模式下,只有少数寄存器可以用作基址(其中之一是B
寄存器)。
从一开始就将地址保留在B
可以避免以后将它们移到B
。 尽管现在寄存器-寄存器移动不需要执行单元,但它们使源代码更难阅读4 。
大多数惯用的寄存器用法在今天已经放宽了5,因为太多的特定用途寄存器减少了编译器可以做的优化(并且溢出到堆栈上是昂贵的)。
CPU 非常复杂,如果您想为速度编写代码,那么您应该只考虑速度指标。 惯用的寄存器用法不是其中之一,因为在微架构级别没有单个A
、 B
或C
寄存器,所以程序员看到的“寄存器”只是一个人类概念(好吧,还有前面-结束概念)。
1以其形式AL 、 AX 、 EAX 、 RAX
2 mov A, [mem]
使用操作码A0或A1 ,而mov B, [mem]
使用8A 1E或8B 1E 。 add
和 similar 也是如此。 in
, out
, div
, mul
强制使用A
。
3但不是去取和解码。
4是否有相当于“意大利面条代码”的数据移动到寄存器中?
5考虑例如各种寻址模式或imul
指令
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.